diff --git a/.travis.yml b/.travis.yml index 25aedbbe0..8a29bd1f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,23 @@ language: java -# jdk: -# - oraclejdk8 +sudo: false +env: + - GRADLE_USER_HOME=gradle-cache + +cache: + directories: + - gradle-cache + - netbeans branches: only: - master +notifications: + slack: + secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" + on_success: change + on_failure: always + before_install: # required libs for android build tools # sudo apt-get update diff --git a/README.md b/README.md index b3f993569..29161ffa4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -jMonkeyEngine +jMonkeyEngine ============= +[![Build Status](https://travis-ci.org/jMonkeyEngine/jmonkeyengine.svg?branch=master)](https://travis-ci.org/jMonkeyEngine/jmonkeyengine) + jMonkeyEngine is a 3D game engine for adventurous Java developers. It’s open source, cross platform and cutting edge. And it is all beautifully documented. The 3.0 branch is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll be frequently submitting stable 3.0.x updates until the major 3.1 version arrives. The engine is used by several commercial game studios and computer-science courses. Here's a taste: diff --git a/common.gradle b/common.gradle index 9d65f7338..6af4c664f 100644 --- a/common.gradle +++ b/common.gradle @@ -4,16 +4,17 @@ apply plugin: 'java' apply plugin: 'maven' +apply plugin: 'maven-publish' -String mavenGroupId = 'com.jme3' -String mavenVersion = jmeVersion + '-' + jmeVersionTag //'-SNAPSHOT' +group = 'com.jme3' +version = jmeVersion + '-' + jmeVersionTag sourceCompatibility = '1.6' [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' repositories { mavenCentral() - maven{ + maven { url "http://nifty-gui.sourceforge.net/nifty-maven-repo" } } @@ -23,11 +24,6 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.10' } -String mavenArtifactId = name - -group = mavenGroupId -version = mavenVersion - javadoc { failOnError = false options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED @@ -60,11 +56,40 @@ artifacts { } } -configure(install.repositories.mavenInstaller) { - pom.project { - groupId = mavenGroupId - artifactId = mavenArtifactId - version = mavenVersion +publishing { + publications { + maven(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + + pom.withXml { + asNode().children().last() + { + resolveStrategy = Closure.DELEGATE_FIRST + name POM_NAME + description POM_DESCRIPTION + url POM_URL + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEVELOPER_CONNECTION + } + licenses { + license { + name POM_LICENSE_NAME + url POM_LICENSE_URL + distribution POM_LICENSE_DISTRIBUTION + } + } + } + } + } + } + + repositories { + maven { + url "${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo + } } } diff --git a/gradle.properties b/gradle.properties index 00429d65a..ccc3ed460 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,3 +23,14 @@ bulletZipFile = bullet.zip # Path for downloading NetBeans Base netbeansUrl = http://download.netbeans.org/netbeans/8.0.2/final/zip/netbeans-8.0.2-201411181905-javase.zip + +# POM settings +POM_NAME=jMonkeyEngine +POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers +POM_URL=http://jmonkeyengine.org +POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine +POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git +POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git +POM_LICENSE_NAME=New BSD (3-clause) License +POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause +POM_LICENSE_DISTRIBUTION=repo 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 index 7ed04658e..394cc257b 100644 --- a/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java @@ -525,4 +525,9 @@ public class AndroidMediaPlayerAudioRenderer implements AudioRenderer, @Override public void deleteFilter(Filter filter) { } + + @Override + public float getSourcePlaybackTime(AudioSource src) { + throw new UnsupportedOperationException("Not supported yet."); + } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java similarity index 51% rename from jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java rename to jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java index d233836a5..2c0367946 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java @@ -35,314 +35,240 @@ 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 + * 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, +public class AndroidGestureProcessor 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; + private static final Logger logger = Logger.getLogger(AndroidGestureProcessor.class.getName()); + + private AndroidTouchInput touchInput; 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)); + public AndroidGestureProcessor(AndroidTouchInput touchInput) { + this.touchInput = touchInput; } - - 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 */ - + + @Override 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())); +// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + gestureDownX = touchInput.getJmeX(event.getX()); + gestureDownY = touchInput.invertY(touchInput.getJmeY(event.getY())); return true; } + @Override 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()}); +// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); return true; } + @Override 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(); +// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); } + @Override 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(); +// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); } + @Override 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. + // 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}); + if (!touchInput.getScaleDetector().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[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.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)); + float jmeX = touchInput.getJmeX(endEvent.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(endEvent.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, touchInput.getJmeX(-distX), touchInput.getJmeY(distY)); + touchEvent.setPointerId(touchInput.getPointerId(endEvent)); touchEvent.setTime(endEvent.getEventTime()); touchEvent.setPressure(endEvent.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); } return true; } + @Override 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(); +// 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[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY}); + + float jmeX = touchInput.getJmeX(startEvent.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(startEvent.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY); - touchEvent.setPointerId(getPointerId(endEvent)); + touchEvent.setPointerId(touchInput.getPointerId(endEvent)); touchEvent.setTime(endEvent.getEventTime()); touchEvent.setPressure(endEvent.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); return true; } /* Events from onDoubleTapListener */ - + + @Override 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(); +// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); return true; } + @Override 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 + // 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(); +// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); return true; } + @Override 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); -// } +// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + if (touchInput.getAction(event) == MotionEvent.ACTION_UP) { + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), touchInput.invertY(event.getY()), 0, 0); + touchEvent.setPointerId(touchInput.getPointerId(event)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure()); + touchInput.addEvent(touchEvent); + } return true; } /* Events from ScaleGestureDetector */ - + + @Override 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"); +// logger.log(Level.INFO, "onScaleBegin"); scaleStartX = gestureDownX; scaleStartY = gestureDownY; - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + TouchEvent touchEvent = touchInput.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); - + touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress()); + touchInput.addEvent(touchEvent); + return true; } + @Override public boolean onScale(ScaleGestureDetector scaleGestureDetector) { // return true or all gestures for this event will be accumulated - logger.log(Level.INFO, "onScale"); +// logger.log(Level.INFO, "onScale"); scaleStartX = gestureDownX; scaleStartY = gestureDownY; - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + TouchEvent touchEvent = touchInput.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); + touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress()); + touchInput.addEvent(touchEvent); return true; } + @Override public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { - logger.log(Level.INFO, "onScaleEnd"); +// logger.log(Level.INFO, "onScaleEnd"); scaleStartX = gestureDownX; scaleStartY = gestureDownY; - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + TouchEvent touchEvent = touchInput.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); + touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress()); + touchInput.addEvent(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 deleted file mode 100644 index 02fef6b0f..000000000 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java +++ /dev/null @@ -1,686 +0,0 @@ -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 boolean isSimulateKeyboard() { - return keyboardEventsEnabled; - } - -} 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 index 202907316..9f4729c66 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -33,231 +33,206 @@ package com.jme3.input.android; import android.opengl.GLSurfaceView; -import android.os.Build; +import android.view.GestureDetector; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; import android.view.View; -import com.jme3.input.RawInputListener; +import com.jme3.input.JoyInput; 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.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. + * inputs to jME. It receives the inputs from the Android View and passes them + * to the appropriate classes based on the source of the input.
+ * This class is to be extended when new functionality is released in Android. * * @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 dontSendHistory = false; +public class AndroidInputHandler implements View.OnTouchListener, + View.OnKeyListener { + private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); - // Internal - private GLSurfaceView 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; + protected GLSurfaceView view; + protected AndroidTouchInput touchInput; + protected AndroidJoyInput joyInput; 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; + touchInput = new AndroidTouchInput(this); + joyInput = new AndroidJoyInput(this); } public void setView(View view) { - if (touchHandler != null) { - touchHandler.setView(view); + if (this.view != null && view != null && this.view.equals(view)) { + return; } - if (keyHandler != null) { - keyHandler.setView(view); - } - if (gestureHandler != null) { - gestureHandler.setView(view); + + if (this.view != null) { + removeListeners(this.view); } + this.view = (GLSurfaceView)view; - } - public View getView() { - return view; - } + if (this.view != null) { + addListeners(this.view); + } - public float invertX(float origX) { - return getJmeX(view.getWidth()) - origX; + joyInput.setView((GLSurfaceView)view); } - public float invertY(float origY) { - return getJmeY(view.getHeight()) - origY; + public View getView() { + return view; } - public float getJmeX(float origX) { - return origX * scaleX; + protected void removeListeners(GLSurfaceView view) { + view.setOnTouchListener(null); + view.setOnKeyListener(null); + touchInput.setGestureDetector(null); + touchInput.setScaleDetector(null); } - public float getJmeY(float origY) { - return origY * scaleY; + protected void addListeners(GLSurfaceView view) { + view.setOnTouchListener(this); + view.setOnKeyListener(this); + AndroidGestureProcessor gestureHandler = new AndroidGestureProcessor(touchInput); + touchInput.setGestureDetector(new GestureDetector( + view.getContext(), gestureHandler)); + touchInput.setScaleDetector(new ScaleGestureDetector( + view.getContext(), gestureHandler)); } public void loadSettings(AppSettings settings) { - keyboardEventsEnabled = settings.isEmulateKeyboard(); - mouseEventsEnabled = settings.isEmulateMouse(); - mouseEventsInvertX = settings.isEmulateMouseFlipX(); - mouseEventsInvertY = settings.isEmulateMouseFlipY(); - - // 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}); + touchInput.loadSettings(settings); + } + + public TouchInput getTouchInput() { + return touchInput; + } + + public JoyInput getJoyInput() { + return joyInput; + } + + /* + * Android input events include the source from which the input came from. + * We must look at the source of the input event to determine which type + * of jME input it belongs to. + * If the input is from a gamepad or joystick source, the event is sent + * to the JoyInput class to convert the event into jME joystick events. + *
+ * If the input is from a touchscreen source, the event is sent to the + * TouchProcessor to convert the event into touch events. + * The TouchProcessor also converts the events into Mouse and Key events + * if AppSettings is set to simulate Mouse or Keyboard events. + * + * Android reports the source as a bitmask as shown below.
+ * + * InputDevice Sources + * 0000 0000 0000 0000 0000 0000 0000 0000 - 32 bit bitmask + * + * 0000 0000 0000 0000 0000 0000 1111 1111 - SOURCE_CLASS_MASK (0x000000ff) + * 0000 0000 0000 0000 0000 0000 0000 0000 - SOURCE_CLASS_NONE (0x00000000) + * 0000 0000 0000 0000 0000 0000 0000 0001 - SOURCE_CLASS_BUTTON (0x00000001) + * 0000 0000 0000 0000 0000 0000 0000 0010 - SOURCE_CLASS_POINTER (0x00000002) + * 0000 0000 0000 0000 0000 0000 0000 0100 - SOURCE_CLASS_TRACKBALL (0x00000004) + * 0000 0000 0000 0000 0000 0000 0000 1000 - SOURCE_CLASS_POSITION (0x00000008) + * 0000 0000 0000 0000 0000 0000 0001 0000 - SOURCE_CLASS_JOYSTICK (0x00000010) + * + * 1111 1111 1111 1111 1111 1111 0000 0000 - Source_Any (0xffffff00) + * 0000 0000 0000 0000 0000 0000 0000 0000 - SOURCE_UNKNOWN (0x00000000) + * 0000 0000 0000 0000 0000 0001 0000 0001 - SOURCE_KEYBOARD (0x00000101) + * 0000 0000 0000 0000 0000 0010 0000 0001 - SOURCE_DPAD (0x00000201) + * 0000 0000 0000 0000 0000 0100 0000 0001 - SOURCE_GAMEPAD (0x00000401) + * 0000 0000 0000 0000 0001 0000 0000 0010 - SOURCE_TOUCHSCREEN (0x00001002) + * 0000 0000 0000 0000 0010 0000 0000 0010 - SOURCE_MOUSE (0x00002002) + * 0000 0000 0000 0000 0100 0000 0000 0010 - SOURCE_STYLUS (0x00004002) + * 0000 0000 0000 0001 0000 0000 0000 0100 - SOURCE_TRACKBALL (0x00010004) + * 0000 0000 0001 0000 0000 0000 0000 1000 - SOURCE_TOUCHPAD (0x00100008) + * 0000 0000 0010 0000 0000 0000 0000 0000 - SOURCE_TOUCH_NAVIGATION (0x00200000) + * 0000 0001 0000 0000 0000 0000 0001 0000 - SOURCE_JOYSTICK (0x01000010) + * 0000 0010 0000 0000 0000 0000 0000 0001 - SOURCE_HDMI (0x02000001) + * + * Example values reported by Android for Source + * 4,098 = 0x00001002 = + * 0000 0000 0000 0000 0001 0000 0000 0010 - SOURCE_CLASS_POINTER + * SOURCE_TOUCHSCREEN + * 1,281 = 0x00000501 = + * 0000 0000 0000 0000 0000 0101 0000 0001 - SOURCE_CLASS_BUTTON + * SOURCE_KEYBOARD + * SOURCE_GAMEPAD + * 16,777,232 = 0x01000010 = + * 0000 0001 0000 0000 0000 0000 0001 0000 - SOURCE_CLASS_JOYSTICK + * SOURCE_JOYSTICK + * + * 16,778,513 = 0x01000511 = + * 0000 0001 0000 0000 0000 0101 0001 0001 - SOURCE_CLASS_BUTTON + * SOURCE_CLASS_JOYSTICK + * SOURCE_GAMEPAD + * SOURCE_KEYBOARD + * SOURCE_JOYSTICK + * + * 257 = 0x00000101 = + * 0000 0000 0000 0000 0000 0001 0000 0001 - SOURCE_CLASS_BUTTON + * SOURCE_KEYBOARD + * + * + * + */ - } - // ----------------------------------------- - // JME3 Input interface @Override - public void initialize() { - touchEventPool.initialize(); - if (touchHandler != null) { - touchHandler.initialize(); - } - if (keyHandler != null) { - keyHandler.initialize(); - } - if (gestureHandler != null) { - gestureHandler.initialize(); + public boolean onTouch(View view, MotionEvent event) { + if (view != getView()) { + return false; } - initialized = true; - } + boolean consumed = false; - @Override - public void destroy() { - initialized = false; + int source = event.getSource(); +// logger.log(Level.INFO, "onTouch source: {0}", source); - 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(); - } + boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN); +// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); - 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); - } - } + if (isTouch && touchInput != null) { + // send the event to the touch processor + consumed = touchInput.onTouch(event); } - } - // ----------------------------------------- + return consumed; - public TouchEvent getFreeTouchEvent() { - return touchEventPool.getNextFreeEvent(); } - public void addEvent(InputEvent event) { - inputEventQueue.add(event); - if (event instanceof TouchEvent) { - touchEventPool.storeEvent((TouchEvent)event); + @Override + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (view != getView()) { + return false; } - } - public void setSimulateMouse(boolean simulate) { - this.mouseEventsEnabled = simulate; - } + boolean consumed = false; - public boolean isSimulateMouse() { - return mouseEventsEnabled; - } + int source = event.getSource(); +// logger.log(Level.INFO, "onKey source: {0}", source); - public boolean isMouseEventsInvertX() { - return mouseEventsInvertX; - } + boolean isTouch = + ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) || + ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD); +// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); - public boolean isMouseEventsInvertY() { - return mouseEventsInvertY; - } - - public void setSimulateKeyboard(boolean simulate) { - this.keyboardEventsEnabled = simulate; - } + if (touchInput != null) { + consumed = touchInput.onKey(event); + } - public boolean isSimulateKeyboard() { - return keyboardEventsEnabled; - } + return consumed; - public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java new file mode 100644 index 000000000..4b39ed24c --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.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.input.android; + +import android.opengl.GLSurfaceView; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidInputHandler14 extends AndroidInputHandler to + * add the onHover and onGenericMotion events that where added in Android rev 14 (Android 4.0).
+ * The onGenericMotion events are the main interface to Joystick axes. They + * were actually released in Android rev 12. + * + * @author iwgeric + */ +public class AndroidInputHandler14 extends AndroidInputHandler implements View.OnHoverListener, + View.OnGenericMotionListener { + + private static final Logger logger = Logger.getLogger(AndroidInputHandler14.class.getName()); + + public AndroidInputHandler14() { + touchInput = new AndroidTouchInput14(this); + joyInput = new AndroidJoyInput14(this); + } + + @Override + protected void removeListeners(GLSurfaceView view) { + super.removeListeners(view); + view.setOnHoverListener(null); + view.setOnGenericMotionListener(null); + } + + @Override + protected void addListeners(GLSurfaceView view) { + super.addListeners(view); + view.setOnHoverListener(this); + view.setOnGenericMotionListener(this); + } + + @Override + public boolean onHover(View view, MotionEvent event) { + if (view != getView()) { + return false; + } + + boolean consumed = false; + + int source = event.getSource(); +// logger.log(Level.INFO, "onTouch source: {0}", source); + + boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN); +// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); + + if (isTouch && touchInput != null) { + // send the event to the touch processor + consumed = ((AndroidTouchInput14)touchInput).onHover(event); + } + + return consumed; + } + + @Override + public boolean onGenericMotion(View view, MotionEvent event) { + if (view != getView()) { + return false; + } + + boolean consumed = false; + + int source = event.getSource(); +// logger.log(Level.INFO, "onGenericMotion source: {0}", source); + + boolean isJoystick = + ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); + + if (isJoystick && joyInput != null) { +// logger.log(Level.INFO, "onGenericMotion source: {0}, isJoystick: {1}", +// new Object[]{source, isJoystick}); + // send the event to the touch processor + consumed = consumed || ((AndroidJoyInput14)joyInput).onGenericMotion(event); + } + + return consumed; + } + + @Override + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (view != getView()) { + return false; + } + + boolean consumed = false; + +// logger.log(Level.INFO, "onKey keyCode: {0}, action: {1}, event: {2}", +// new Object[]{KeyEvent.keyCodeToString(keyCode), event.getAction(), event}); + int source = event.getSource(); +// logger.log(Level.INFO, "onKey source: {0}", source); + + boolean isTouch = + ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) || + ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD); + boolean isJoystick = + ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); + + if (isTouch && touchInput != null) { +// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); + consumed = touchInput.onKey(event); + } + if (isJoystick && joyInput != null) { +// logger.log(Level.INFO, "onKey source: {0}, isJoystick: {1}", +// new Object[]{source, isJoystick}); + // use inclusive OR to make sure the onKey method is called. + consumed = consumed | ((AndroidJoyInput14)joyInput).onKey(event); + } + + return consumed; + + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java similarity index 83% rename from jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java rename to jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java index 2c3a1d74e..1e610d24e 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java @@ -33,9 +33,7 @@ package com.jme3.input.android; import android.content.Context; import android.opengl.GLSurfaceView; -import android.os.Build; import android.os.Vibrator; -import android.view.View; import com.jme3.input.InputManager; import com.jme3.input.JoyInput; import com.jme3.input.Joystick; @@ -79,15 +77,16 @@ import java.util.logging.Logger; * * @author iwgeric */ -public class AndroidJoyInputHandler implements JoyInput { - private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName()); +public class AndroidJoyInput implements JoyInput { + private static final Logger logger = Logger.getLogger(AndroidJoyInput.class.getName()); + public static boolean disableSensors = false; - private List joystickList = new ArrayList(); + protected AndroidInputHandler inputHandler; + protected List joystickList = new ArrayList(); // private boolean dontSendHistory = false; // Internal - private GLSurfaceView view; private boolean initialized = false; private RawInputListener listener = null; private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue(); @@ -96,34 +95,29 @@ public class AndroidJoyInputHandler implements JoyInput { private boolean vibratorActive = false; private long maxRumbleTime = 250; // 250ms - public AndroidJoyInputHandler() { - int buildVersion = Build.VERSION.SDK_INT; - logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); -// if (buildVersion >= 14) { -// touchHandler = new AndroidTouchHandler14(this); -// } else if (buildVersion >= 8){ -// touchHandler = new AndroidTouchHandler(this); -// } + public AndroidJoyInput(AndroidInputHandler inputHandler) { + this.inputHandler = inputHandler; sensorJoyInput = new AndroidSensorJoyInput(this); } public void setView(GLSurfaceView view) { -// if (touchHandler != null) { -// touchHandler.setView(view); -// } + if (view == null) { + vibrator = null; + } else { + // Get instance of Vibrator from current Context + vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null) { + logger.log(Level.FINE, "Vibrator Service not found."); + } + } + if (sensorJoyInput != null) { sensorJoyInput.setView(view); } - this.view = (GLSurfaceView)view; - - } - - public View getView() { - return view; } public void loadSettings(AppSettings settings) { -// sensorEventsEnabled = settings.useSensors(); + } public void addEvent(InputEvent event) { @@ -155,20 +149,8 @@ public class AndroidJoyInputHandler implements JoyInput { } - - - - @Override public void initialize() { -// if (sensorJoyInput != null) { -// sensorJoyInput.initialize(); -// } - // Get instance of Vibrator from current Context - vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator == null) { - logger.log(Level.FINE, "Vibrator Service not found."); - } initialized = true; } @@ -211,8 +193,8 @@ public class AndroidJoyInputHandler implements JoyInput { }; final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from - logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", - new Object[]{amount, rumbleOnDur, rumbleOffDur}); +// logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", +// new Object[]{amount, rumbleOnDur, rumbleOffDur}); if (rumbleOnDur > 0) { vibrator.vibrate(rumblePattern, rumbleRepeatFrom); @@ -226,9 +208,10 @@ public class AndroidJoyInputHandler implements JoyInput { @Override public Joystick[] loadJoysticks(InputManager inputManager) { - joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); - - + logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName()); + if (!disableSensors) { + joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); + } return joystickList.toArray( new Joystick[joystickList.size()] ); } @@ -252,6 +235,4 @@ public class AndroidJoyInputHandler implements JoyInput { } - - } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java new file mode 100644 index 000000000..00478aea1 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.KeyEvent; +import android.view.MotionEvent; +import com.jme3.input.InputManager; +import com.jme3.input.Joystick; +import java.util.logging.Logger; + +/** + * AndroidJoyInput14 extends AndroidJoyInput + * to include support for physical joysticks/gamepads.
+ * + * @author iwgeric + */ +public class AndroidJoyInput14 extends AndroidJoyInput { + private static final Logger logger = Logger.getLogger(AndroidJoyInput14.class.getName()); + + private AndroidJoystickJoyInput14 joystickJoyInput; + + public AndroidJoyInput14(AndroidInputHandler inputHandler) { + super(inputHandler); + joystickJoyInput = new AndroidJoystickJoyInput14(this); + } + + /** + * Pauses the joystick device listeners to save battery life if they are not needed. + * Used to pause when the activity pauses + */ + @Override + public void pauseJoysticks() { + super.pauseJoysticks(); + + if (joystickJoyInput != null) { + joystickJoyInput.pauseJoysticks(); + } + } + + /** + * Resumes the joystick device listeners. + * Used to resume when the activity comes to the top of the stack + */ + @Override + public void resumeJoysticks() { + super.resumeJoysticks(); + if (joystickJoyInput != null) { + joystickJoyInput.resumeJoysticks(); + } + + } + + @Override + public void destroy() { + super.destroy(); + if (joystickJoyInput != null) { + joystickJoyInput.destroy(); + } + } + + @Override + public Joystick[] loadJoysticks(InputManager inputManager) { + // load the simulated joystick for device orientation + super.loadJoysticks(inputManager); + // load physical gamepads/joysticks + joystickList.addAll(joystickJoyInput.loadJoysticks(joystickList.size(), inputManager)); + // return the list of joysticks back to InputManager + return joystickList.toArray( new Joystick[joystickList.size()] ); + } + + public boolean onGenericMotion(MotionEvent event) { + return joystickJoyInput.onGenericMotion(event); + } + + public boolean onKey(KeyEvent event) { + return joystickJoyInput.onKey(event); + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java new file mode 100644 index 000000000..1ed98977c --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +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.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.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Main class that creates and manages Android inputs for physical gamepads/joysticks. + * + * @author iwgeric + */ +public class AndroidJoystickJoyInput14 { + private static final Logger logger = Logger.getLogger(AndroidJoystickJoyInput14.class.getName()); + + private boolean loaded = false; + private AndroidJoyInput joyInput; + private Map joystickIndex = new HashMap(); + + private static int[] AndroidGamepadButtons = { + // Dpad buttons + KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_CENTER, + + // pressing joystick down + KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR, + + // buttons + KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, + + // buttons on back of device + KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_BUTTON_R1, + KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2, + + // start / select buttons + KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_SELECT, + KeyEvent.KEYCODE_BUTTON_MODE, + + }; + + public AndroidJoystickJoyInput14(AndroidJoyInput joyInput) { + this.joyInput = joyInput; + } + + + public void pauseJoysticks() { + + } + + public void resumeJoysticks() { + + } + + public void destroy() { + + } + + public List loadJoysticks(int joyId, InputManager inputManager) { + logger.log(Level.INFO, "loading Joystick devices"); + ArrayList joysticks = new ArrayList(); + joysticks.clear(); + joystickIndex.clear(); + + ArrayList gameControllerDeviceIds = new ArrayList(); + int[] deviceIds = InputDevice.getDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice dev = InputDevice.getDevice(deviceId); + int sources = dev.getSources(); + logger.log(Level.FINE, "deviceId[{0}] sources: {1}", new Object[]{deviceId, sources}); + + // Verify that the device has gamepad buttons, control sticks, or both. + if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { + // This device is a game controller. Store its device ID. + if (!gameControllerDeviceIds.contains(deviceId)) { + gameControllerDeviceIds.add(deviceId); + logger.log(Level.FINE, "Attempting to create joystick for device: {0}", dev); + // Create an AndroidJoystick and store the InputDevice so we + // can later correspond the input from the InputDevice to the + // appropriate jME Joystick event + AndroidJoystick joystick = new AndroidJoystick(inputManager, + joyInput, + dev, + joyId+joysticks.size(), + dev.getName()); + joystickIndex.put(deviceId, joystick); + joysticks.add(joystick); + + // Each analog input is reported as a MotionRange + // The axis number corresponds to the type of axis + // The AndroidJoystick.addAxis(MotionRange) converts the axis + // type reported by Android into the jME Joystick axis + List motionRanges = dev.getMotionRanges(); + for (MotionRange motionRange: motionRanges) { + logger.log(Level.INFO, "motion range: {0}", motionRange.toString()); + logger.log(Level.INFO, "axis: {0}", motionRange.getAxis()); + JoystickAxis axis = joystick.addAxis(motionRange); + logger.log(Level.INFO, "added axis: {0}", axis); + } + + // InputDevice has a method for determining if a keyCode is + // supported (InputDevice public boolean[] hasKeys (int... keys)). + // But this method wasn't added until rev 19 (Android 4.4) + // Therefore, we only can query the entire device and see if + // any InputDevice supports the keyCode. This may result in + // buttons being configured that don't exist on the specific + // device, but I haven't found a better way yet. + for (int keyCode: AndroidGamepadButtons) { + logger.log(Level.INFO, "button[{0}]: {1}", + new Object[]{keyCode, KeyCharacterMap.deviceHasKey(keyCode)}); + if (KeyCharacterMap.deviceHasKey(keyCode)) { + // add button even though we aren't sure if the button + // actually exists on this InputDevice + logger.log(Level.INFO, "button[{0}] exists somewhere", keyCode); + JoystickButton button = joystick.addButton(keyCode); + logger.log(Level.INFO, "added button: {0}", button); + } + } + + } + } + } + + + loaded = true; + return joysticks; + } + + public boolean onGenericMotion(MotionEvent event) { + boolean consumed = false; +// logger.log(Level.INFO, "onGenericMotion event: {0}", event); + event.getDeviceId(); + event.getSource(); +// logger.log(Level.INFO, "deviceId: {0}, source: {1}", new Object[]{event.getDeviceId(), event.getSource()}); + AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); + if (joystick != null) { + for (int androidAxis: joystick.getAndroidAxes()) { + String axisName = MotionEvent.axisToString(androidAxis); + float value = event.getAxisValue(androidAxis); + int action = event.getAction(); + if (action == MotionEvent.ACTION_MOVE) { +// logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}", +// new Object[]{androidAxis, axisName, value}); + JoystickAxis axis = joystick.getAxis(androidAxis); + if (axis != null) { +// logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}, deadzone: {3}", +// new Object[]{androidAxis, axisName, value, axis.getDeadZone()}); + JoyAxisEvent axisEvent = new JoyAxisEvent(axis, value); + joyInput.addEvent(axisEvent); + consumed = true; + } else { +// logger.log(Level.INFO, "axis was null for axisName: {0}", axisName); + } + } else { +// logger.log(Level.INFO, "action: {0}", action); + } + } + } + + return consumed; + } + + public boolean onKey(KeyEvent event) { + boolean consumed = false; +// logger.log(Level.INFO, "onKey event: {0}", event); + + event.getDeviceId(); + event.getSource(); + AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); + if (joystick != null) { + JoystickButton button = joystick.getButton(event.getKeyCode()); + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; + if (button != null) { + JoyButtonEvent buttonEvent = new JoyButtonEvent(button, pressed); + joyInput.addEvent(buttonEvent); + consumed = true; + } else { + JoystickButton newButton = joystick.addButton(event.getKeyCode()); + JoyButtonEvent buttonEvent = new JoyButtonEvent(newButton, pressed); + joyInput.addEvent(buttonEvent); + consumed = true; + } + } + + return consumed; + } + + protected class AndroidJoystick extends AbstractJoystick { + + private JoystickAxis nullAxis; + private InputDevice device; + private JoystickAxis xAxis; + private JoystickAxis yAxis; + private JoystickAxis povX; + private JoystickAxis povY; + private Map axisIndex = new HashMap(); + private Map buttonIndex = new HashMap(); + + public AndroidJoystick( InputManager inputManager, JoyInput joyInput, InputDevice device, + int joyId, String name ) { + super( inputManager, joyInput, joyId, name ); + + this.device = device; + + 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 JoystickAxis getAxis(int androidAxis) { + return axisIndex.get(androidAxis); + } + + protected Set getAndroidAxes() { + return axisIndex.keySet(); + } + + protected JoystickButton getButton(int keyCode) { + return buttonIndex.get(keyCode); + } + + protected JoystickButton addButton( int keyCode ) { + +// logger.log(Level.FINE, "Adding button: {0}", keyCode); + + String name = KeyEvent.keyCodeToString(keyCode); + String original = KeyEvent.keyCodeToString(keyCode); + // A/B/X/Y buttons + if (keyCode == KeyEvent.KEYCODE_BUTTON_Y) { + original = JoystickButton.BUTTON_0; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_B) { + original = JoystickButton.BUTTON_1; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) { + original = JoystickButton.BUTTON_2; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_X) { + original = JoystickButton.BUTTON_3; + // Front buttons Some of these have the top ones and the bottoms ones flipped. + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1) { + original = JoystickButton.BUTTON_4; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1) { + original = JoystickButton.BUTTON_5; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L2) { + original = JoystickButton.BUTTON_6; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R2) { + original = JoystickButton.BUTTON_7; +// // Dpad buttons +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// original = JoystickButton.BUTTON_8; +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// original = JoystickButton.BUTTON_9; +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// original = JoystickButton.BUTTON_8; +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// original = JoystickButton.BUTTON_9; + // Select and start buttons + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) { + original = JoystickButton.BUTTON_8; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_START) { + original = JoystickButton.BUTTON_9; + // Joystick push buttons + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL) { + original = JoystickButton.BUTTON_10; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR) { + original = JoystickButton.BUTTON_11; + } + + String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); + if( logicalId == null ? original != null : !logicalId.equals(original) ) { + logger.log(Level.FINE, "Remapped: {0} to: {1}", + new Object[]{original, logicalId}); + } + + JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), + name, logicalId ); + addButton(button); + buttonIndex.put( keyCode, button ); + return button; + } + + protected JoystickAxis addAxis(MotionRange motionRange) { + + String name = MotionEvent.axisToString(motionRange.getAxis()); + + String original = MotionEvent.axisToString(motionRange.getAxis()); + if (motionRange.getAxis() == MotionEvent.AXIS_X) { + original = JoystickAxis.X_AXIS; + } else if (motionRange.getAxis() == MotionEvent.AXIS_Y) { + original = JoystickAxis.Y_AXIS; + } else if (motionRange.getAxis() == MotionEvent.AXIS_Z) { + original = JoystickAxis.Z_AXIS; + } else if (motionRange.getAxis() == MotionEvent.AXIS_RZ) { + original = JoystickAxis.Z_ROTATION; + } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { + original = JoystickAxis.POV_X; + } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { + original = JoystickAxis.POV_Y; + } + String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); + if( logicalId == null ? original != null : !logicalId.equals(original) ) { + logger.log(Level.FINE, "Remapped: {0} to: {1}", + new Object[]{original, logicalId}); + } + + JoystickAxis axis = new DefaultJoystickAxis(getInputManager(), + this, + getAxisCount(), + name, + logicalId, + true, + true, + motionRange.getFlat()); + + if (motionRange.getAxis() == MotionEvent.AXIS_X) { + xAxis = axis; + } + if (motionRange.getAxis() == MotionEvent.AXIS_Y) { + yAxis = axis; + } + if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { + povX = axis; + } + if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { + povY = axis; + } + + addAxis(axis); + axisIndex.put(motionRange.getAxis(), axis); + return axis; + } + + @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-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java deleted file mode 100644 index 997974c31..000000000 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR 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); - - } - - if (androidInput.isSimulateKeyboard()) { - 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; - } - - } - -} 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 index e99d8e9fc..32d1e0008 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java @@ -37,13 +37,14 @@ 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. - * + * jME KeyCodes so that events 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 @@ -141,9 +142,13 @@ public class AndroidKeyMapping { 0x0,//media fastforward 0x0,//mute }; - + public static int getJmeKey(int androidKey) { - return ANDROID_TO_JME[androidKey]; + if (androidKey > ANDROID_TO_JME.length) { + return androidKey; + } else { + 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 index 823aa3d9d..cdd7e6494 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java @@ -75,15 +75,15 @@ import java.util.logging.Logger; public class AndroidSensorJoyInput implements SensorEventListener { private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName()); - private AndroidJoyInputHandler joyHandler; + private AndroidJoyInput joyInput; private SensorManager sensorManager = null; private WindowManager windowManager = null; private IntMap sensors = new IntMap(); private int lastRotation = 0; private boolean loaded = false; - public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) { - this.joyHandler = joyHandler; + public AndroidSensorJoyInput(AndroidJoyInput joyInput) { + this.joyInput = joyInput; } /** @@ -96,7 +96,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { int sensorAccuracy = -1; float[] lastValues; final Object valuesLock = new Object(); - ArrayList axes = new ArrayList(); + ArrayList axes = new ArrayList(); boolean enabled = false; boolean haveData = false; @@ -306,7 +306,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { */ private boolean updateOrientation() { SensorData sensorData; - AndroidJoystickAxis axis; + AndroidSensorJoystickAxis axis; final float[] curInclinationMat = new float[16]; final float[] curRotationMat = new float[16]; final float[] rotatedRotationMat = new float[16]; @@ -374,7 +374,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { sensorData.haveData = true; } else { if (axis.isChanged()) { - joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); + joyInput.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); } } } @@ -401,10 +401,10 @@ public class AndroidSensorJoyInput implements SensorEventListener { public Joystick loadJoystick(int joyId, InputManager inputManager) { SensorData sensorData; - AndroidJoystickAxis axis; + AndroidSensorJoystickAxis axis; - AndroidJoystick joystick = new AndroidJoystick(inputManager, - joyHandler, + AndroidSensorJoystick joystick = new AndroidSensorJoystick(inputManager, + joyInput, joyId, "AndroidSensorsJoystick"); @@ -522,15 +522,15 @@ public class AndroidSensorJoyInput implements SensorEventListener { if (!loaded) { return; } - logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}", - new Object[]{se.sensor.getName(), se.accuracy, se.values}); +// logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}", +// new Object[]{se.sensor.getName(), se.accuracy, se.values}); int sensorType = se.sensor.getType(); SensorData sensorData = sensors.get(sensorType); if (sensorData != null) { - logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}", - new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE}); +// logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}", +// new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE}); } if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) { @@ -543,8 +543,8 @@ public class AndroidSensorJoyInput implements SensorEventListener { } } - if (sensorData != null && sensorData.axes.size() > 0) { - AndroidJoystickAxis axis; + if (sensorData.axes.size() > 0) { + AndroidSensorJoystickAxis axis; for (int i=0; i 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/AndroidTouchInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java new file mode 100644 index 000000000..bddc5fab3 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.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.input.android; + +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +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 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 com.jme3.system.AppSettings; +import java.util.HashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidTouchInput 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 AndroidTouchInput implements TouchInput { + private static final Logger logger = Logger.getLogger(AndroidTouchInput.class.getName()); + + private boolean mouseEventsEnabled = true; + private boolean mouseEventsInvertX = false; + private boolean mouseEventsInvertY = false; + private boolean keyboardEventsEnabled = false; + private boolean dontSendHistory = false; + + protected int numPointers = 0; + final private HashMap lastPositions = new HashMap(); + final 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; + + private boolean initialized = false; + private RawInputListener listener = null; + + private GestureDetector gestureDetector; + private ScaleGestureDetector scaleDetector; + + protected AndroidInputHandler androidInput; + + public AndroidTouchInput(AndroidInputHandler androidInput) { + this.androidInput = androidInput; + } + + public GestureDetector getGestureDetector() { + return gestureDetector; + } + + public void setGestureDetector(GestureDetector gestureDetector) { + this.gestureDetector = gestureDetector; + } + + public ScaleGestureDetector getScaleDetector() { + return scaleDetector; + } + + public void setScaleDetector(ScaleGestureDetector scaleDetector) { + this.scaleDetector = scaleDetector; + } + + public float invertX(float origX) { + return getJmeX(androidInput.getView().getWidth()) - origX; + } + + public float invertY(float origY) { + return getJmeY(androidInput.getView().getHeight()) - origY; + } + + public float getJmeX(float origX) { + return origX * scaleX; + } + + public float getJmeY(float origY) { + return origY * scaleY; + } + + public void loadSettings(AppSettings settings) { + keyboardEventsEnabled = settings.isEmulateKeyboard(); + mouseEventsEnabled = settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + + // view width and height are 0 until the view is displayed on the screen + if (androidInput.getView().getWidth() != 0 && androidInput.getView().getHeight() != 0) { + scaleX = (float)settings.getWidth() / (float)androidInput.getView().getWidth(); + scaleY = (float)settings.getHeight() / (float)androidInput.getView().getHeight(); + } + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", + new Object[]{scaleX, scaleY}); + + + } + + + 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; + } + + public boolean onTouch(MotionEvent event) { + if (!isInitialized()) { + 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 = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touch = 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); + + addEvent(touch); + addEvent(generateMouseEvent(touch)); + + bWasHandled = true; + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touch = 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); + + addEvent(touch); + addEvent(generateMouseEvent(touch)); + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + jmeX = getJmeX(event.getX(p)); + jmeY = invertY(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 = 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); + + addEvent(touch); + addEvent(generateMouseEvent(touch)); + + bWasHandled = true; + } + } + break; + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + if (gestureDetector != null) { + gestureDetector.onTouchEvent(event); + } + if (scaleDetector != null) { + scaleDetector.onTouchEvent(event); + } + + return bWasHandled; + } + + // TODO: Ring Buffer for mouse events? + public InputEvent generateMouseEvent(TouchEvent event) { + InputEvent inputEvent = null; + int newX; + int newY; + int newDX; + int newDY; + + // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events + if (!isSimulateMouse() || numPointers > 1) { + return null; + } + + + if (isMouseEventsInvertX()) { + newX = (int) (invertX(event.getX())); + newDX = (int)event.getDeltaX() * -1; + } else { + newX = (int) event.getX(); + newDX = (int)event.getDeltaX(); + } + + if (isMouseEventsInvertY()) { + newY = (int) (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; + } + + + public boolean onKey(KeyEvent event) { + if (!isInitialized()) { + 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(event.getKeyCode()); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + addEvent(evt); + + } else if (event.getAction() == KeyEvent.ACTION_UP) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(event.getKeyCode()); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + addEvent(evt); + + } + + if (isSimulateKeyboard()) { + KeyInputEvent kie; + char unicodeChar = (char)event.getUnicodeChar(); + int jmeKeyCode = AndroidKeyMapping.getJmeKey(event.getKeyCode()); + + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; + boolean repeating = pressed && event.getRepeatCount() > 0; + + kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating); + kie.setTime(event.getEventTime()); + addEvent(kie); +// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}", +// new Object[]{event.getKeyCode(), 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 ((event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) || + (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) || + (event.getKeyCode() == KeyEvent.KEYCODE_MENU)) { + return false; + } else { + return true; + } + + } + + + + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + touchEventPool.initialize(); + + initialized = true; + } + + @Override + public void destroy() { + initialized = false; + + touchEventPool.destroy(); + + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + + @Override + 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) { + if (event == null) { + return; + } + + logger.log(Level.INFO, "event: {0}", event); + + inputEventQueue.add(event); + if (event instanceof TouchEvent) { + touchEventPool.storeEvent((TouchEvent)event); + } + + } + + @Override + public void setSimulateMouse(boolean simulate) { + this.mouseEventsEnabled = simulate; + } + + @Override + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + @Override + public void setSimulateKeyboard(boolean simulate) { + this.keyboardEventsEnabled = simulate; + } + + @Override + public boolean isSimulateKeyboard() { + return keyboardEventsEnabled; + } + + @Override + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java similarity index 67% rename from jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java rename to jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java index 1a785a5e9..617a4b719 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java @@ -33,7 +33,6 @@ 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; @@ -41,36 +40,20 @@ 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). - * + * AndroidTouchHandler14 extends AndroidTouchHandler to process the onHover + * events added in 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()); +public class AndroidTouchInput14 extends AndroidTouchInput { + private static final Logger logger = Logger.getLogger(AndroidTouchInput14.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 AndroidTouchInput14(AndroidInputHandler androidInput) { + super(androidInput); } - - public boolean onHover(View view, MotionEvent event) { - if (view == null || view != androidInput.getView()) { - return false; - } - + + public boolean onHover(MotionEvent event) { boolean consumed = false; int action = getAction(event); int pointerId = getPointerId(event); @@ -78,34 +61,34 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements 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()}); + +// 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(); + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touchEvent = 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); + + addEvent(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))); + jmeX = getJmeX(event.getX(p)); + jmeY = invertY(getJmeY(event.getY(p))); lastPos = lastHoverPositions.get(event.getPointerId(p)); if (lastPos == null) { lastPos = new Vector2f(jmeX, jmeY); @@ -115,38 +98,39 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements float dX = jmeX - lastPos.x; float dY = jmeY - lastPos.y; if (dX != 0 || dY != 0) { - touchEvent = androidInput.getFreeTouchEvent(); + touchEvent = 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); + addEvent(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(); + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touchEvent = 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); + addEvent(touchEvent); consumed = true; break; default: consumed = false; break; } - + return consumed; + } - + } diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java index 2a7b54023..b77c4a36d 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java @@ -35,39 +35,43 @@ import android.opengl.GLES20; import com.jme3.renderer.RendererException; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GLExt; +import com.jme3.renderer.opengl.GLFbo; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; -public class AndroidGL implements GL, GLExt { +public class AndroidGL implements GL, GLExt, GLFbo { + + public void resetStats() { + } private static int getLimitBytes(ByteBuffer buffer) { checkLimit(buffer); return buffer.limit(); } - + private static int getLimitBytes(ShortBuffer buffer) { checkLimit(buffer); return buffer.limit() * 2; } - + private static int getLimitBytes(IntBuffer buffer) { checkLimit(buffer); return buffer.limit() * 4; } - + private static int getLimitBytes(FloatBuffer buffer) { checkLimit(buffer); return buffer.limit() * 4; } - + private static int getLimitCount(Buffer buffer, int elementSize) { checkLimit(buffer); return buffer.limit() / elementSize; } - + private static void checkLimit(Buffer buffer) { if (buffer == null) { return; @@ -79,7 +83,7 @@ public class AndroidGL implements GL, GLExt { throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); } } - + public void glActiveTexture(int texture) { GLES20.glActiveTexture(texture); } @@ -127,7 +131,7 @@ public class AndroidGL implements GL, GLExt { public void glBufferSubData(int target, long offset, ByteBuffer data) { GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } - + public void glGetBufferSubData(int target, long offset, ByteBuffer data) { throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); } 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 index 5ef866188..991ad25c0 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -47,13 +47,14 @@ import android.widget.EditText; import android.widget.FrameLayout; import com.jme3.input.*; import com.jme3.input.android.AndroidInputHandler; -import com.jme3.input.android.AndroidJoyInputHandler; +import com.jme3.input.android.AndroidInputHandler14; import com.jme3.input.controls.SoftTextDialogInputListener; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; import com.jme3.renderer.android.AndroidGL; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GLExt; +import com.jme3.renderer.opengl.GLFbo; import com.jme3.renderer.opengl.GLRenderer; import com.jme3.system.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -75,7 +76,6 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInputHandler androidInput; - protected AndroidJoyInputHandler androidJoyInput = null; protected long minFrameDuration = 0; // No FPS cap protected long lastUpdateTime = 0; @@ -111,18 +111,17 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex // Start to set up the view GLSurfaceView view = new GLSurfaceView(context); + logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT); if (androidInput == null) { - androidInput = new AndroidInputHandler(); + if (Build.VERSION.SDK_INT >= 14) { + androidInput = new AndroidInputHandler14(); + } else if (Build.VERSION.SDK_INT >= 9){ + androidInput = new AndroidInputHandler(); + } } androidInput.setView(view); androidInput.loadSettings(settings); - if (androidJoyInput == null) { - androidJoyInput = new AndroidJoyInputHandler(); - } - androidJoyInput.setView(view); - androidJoyInput.loadSettings(settings); - // setEGLContextClientVersion must be set before calling setRenderer // this means it cannot be set in AndroidConfigChooser (too late) view.setEGLContextClientVersion(2); @@ -198,7 +197,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex Object gl = new AndroidGL(); // gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl); // gl = new GLDebugES((GL)gl, (GLExt)gl); - renderer = new GLRenderer((GL)gl, (GLExt)gl); + renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl); renderer.initialize(); JmeSystem.setSoftTextDialogInput(this); @@ -235,9 +234,6 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex if (androidInput != null) { androidInput.loadSettings(settings); } - if (androidJoyInput != null) { - androidJoyInput.loadSettings(settings); - } if (settings.getFrameRate() > 0) { minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms @@ -274,12 +270,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex @Override public JoyInput getJoyInput() { - return androidJoyInput; + return androidInput.getJoyInput(); } @Override public TouchInput getTouchInput() { - return androidInput; + return androidInput.getTouchInput(); } @Override 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 index f9be6a6ad..3ecf7fd6b 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp @@ -528,6 +528,18 @@ extern "C" { space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration); return; } + + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setSolverNumIterations + (JNIEnv *env, jobject object, jlong spaceId, jint value) { + 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->getDynamicsWorld()->getSolverInfo().m_numIterations = value; + } #ifdef __cplusplus } 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 index b499ff04c..0380c17b0 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h @@ -174,6 +174,14 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native (JNIEnv *, jobject, jlong, jobject, jobject, jlong, jobject, jfloat); +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: setSolverNumIterations + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setSolverNumIterations +(JNIEnv *, jobject, jlong, jint); + #ifdef __cplusplus } #endif diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java index 05aa9a926..ae278c871 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java @@ -166,7 +166,7 @@ public class BulletAppState implements AppState, PhysicsTickListener { pSpace.addTickListener(this); initialized = true; } - + public void stopPhysics() { if(!initialized){ return; @@ -226,19 +226,9 @@ public class BulletAppState implements AppState, PhysicsTickListener { 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; 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 index c4c0f3995..baad952a0 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java @@ -195,7 +195,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl /** * When set to true, the physics coordinates will be applied to the local - * translation of the Spatial instead of the world traslation. + * translation of the Spatial instead of the world translation. * @param applyPhysicsLocal */ public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 380dc3e16..edea485ef 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -102,7 +102,7 @@ public class PhysicsSpace { private Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f); private float accuracy = 1f / 60f; private int maxSubSteps = 4, rayTestFlags = 1 << 2; - private AssetManager debugManager; + private int solverNumIterations = 10; static { // System.loadLibrary("bulletjme"); @@ -702,7 +702,7 @@ public class PhysicsSpace { public Vector3f getGravity(Vector3f gravity) { return gravity.set(this.gravity); } - + // /** // * applies gravity value to all objects // */ @@ -783,7 +783,7 @@ public class PhysicsSpace { 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. @@ -792,7 +792,7 @@ public class PhysicsSpace { public int GetRayTestFlags() { return rayTestFlags; } - + /** * Performs a ray collision test and returns the results as a list of * PhysicsRayTestResults @@ -837,7 +837,7 @@ public class PhysicsSpace { return (List) results; } - public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { + public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { return sweepTest(shape, start, end, results, 0.0f); } @@ -849,7 +849,7 @@ public class PhysicsSpace { * 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, float allowedCcdPenetration ) { + public List sweepTest(CollisionShape shape, Transform start, Transform end, List results, float allowedCcdPenetration ) { results.clear(); sweepTest_native(shape.getObjectId(), start, end, physicsSpaceId, results, allowedCcdPenetration); return results; @@ -960,28 +960,28 @@ public class PhysicsSpace { } /** - * 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 + * Set the number of iterations used by the contact solver. + * + * The default is 10. Use 4 for low quality, 20 for high quality. + * + * @param numIterations The number of iterations used by the contact & constraint solver. */ - @Deprecated - public void enableDebug(AssetManager manager) { - debugManager = manager; + public void setSolverNumIterations(int numIterations) { + this.solverNumIterations = numIterations; + setSolverNumIterations(physicsSpaceId, numIterations); } - + /** - * Disable debug display + * Get the number of iterations used by the contact solver. + * + * @return The number of iterations used by the contact & constraint solver. */ - public void disableDebug() { - debugManager = null; - } - - public AssetManager getDebugManager() { - return debugManager; + public int getSolverNumIterations() { + return solverNumIterations; } - + + private static native void setSolverNumIterations(long physicsSpaceId, int numIterations); + public static native void initNativePhysics(); /** diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index 819a77383..a78b4b931 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -313,6 +313,26 @@ public final class Bone implements Savable { return modelBindInverseScale; } + public Transform getModelBindInverseTransform() { + Transform t = new Transform(); + t.setTranslation(modelBindInversePos); + t.setRotation(modelBindInverseRot); + if (modelBindInverseScale != null) { + t.setScale(modelBindInverseScale); + } + return t; + } + + public Transform getBindInverseTransform() { + Transform t = new Transform(); + t.setTranslation(bindPos); + t.setRotation(bindRot); + if (bindScale != null) { + t.setScale(bindScale); + } + return t.invert(); + } + /** * @deprecated use {@link #getBindPosition()} */ diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index d5ef31939..b1f3d02df 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -82,7 +82,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { /** * User wishes to use hardware skinning if available. */ - private transient boolean hwSkinningDesired = false; + private transient boolean hwSkinningDesired = true; /** * Hardware skinning is currently being used. @@ -347,11 +347,22 @@ public class SkeletonControl extends AbstractControl implements Cloneable { public Control cloneForSpatial(Spatial spatial) { Node clonedNode = (Node) spatial; - AnimControl ctrl = spatial.getControl(AnimControl.class); SkeletonControl clone = new SkeletonControl(); - clone.skeleton = ctrl.getSkeleton(); - + AnimControl ctrl = spatial.getControl(AnimControl.class); + if (ctrl != null) { + // AnimControl is responsible for cloning the skeleton, not + // SkeletonControl. + clone.skeleton = ctrl.getSkeleton(); + } else { + // If there's no AnimControl, create the clone ourselves. + clone.skeleton = new Skeleton(skeleton); + } + clone.hwSkinningDesired = this.hwSkinningDesired; + clone.hwSkinningEnabled = this.hwSkinningEnabled; + clone.hwSkinningSupported = this.hwSkinningSupported; + clone.hwSkinningTested = this.hwSkinningTested; + clone.setSpatial(clonedNode); // Fix attachments for the cloned node diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index 9f748fdf4..a0446e85e 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -60,7 +60,7 @@ import com.jme3.scene.control.Control; */ public class StatsView extends Node implements Control { - private BitmapText[] labels; + private BitmapText statText; private Statistics statistics; private String[] statLabels; @@ -81,20 +81,17 @@ public class StatsView extends Node implements Control { 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]); - } + statText = new BitmapText(font); + statText.setLocalTranslation(0, statText.getLineHeight() * statLabels.length, 0); + attachChild(statText); addControl(this); } public float getHeight() { - return labels[0].getLineHeight() * statLabels.length; + return statText.getLineHeight() * statLabels.length; } public void update(float tpf) { @@ -103,11 +100,14 @@ public class StatsView extends Node implements Control { 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); + stringBuilder.setLength(0); + + // Need to walk through it backwards, as the first label + // should appear at the bottom, not the top. + for (int i = statLabels.length - 1; i >= 0; i--) { + stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n'); } + statText.setText(stringBuilder); // Moved to ResetStatsState to make sure it is // done even if there is no StatsView or the StatsView diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 0e75db9fe..8664af6c7 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -77,7 +77,7 @@ public class AudioNode extends Node implements AudioSource { 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 boolean reverbEnabled = false; protected float maxDistance = 200; // 200 meters protected float refDistance = 10; // 10 meters protected Filter reverbFilter; @@ -409,6 +409,14 @@ public class AudioNode extends Node implements AudioSource { play(); } } + + @Override + public float getPlaybackTime() { + if (channel >= 0) + return getRenderer().getSourcePlaybackTime(this); + else + return 0; + } public Vector3f getPosition() { return getWorldTranslation(); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java index 78ea88e91..695999e48 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java @@ -59,6 +59,7 @@ public interface AudioRenderer { public void updateSourceParam(AudioSource src, AudioParam param); public void updateListenerParam(Listener listener, ListenerParam param); + public float getSourcePlaybackTime(AudioSource src); public void deleteFilter(Filter filter); public void deleteAudioData(AudioData ad); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java index 3aa23b78d..75a4e70f9 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java @@ -95,6 +95,11 @@ public interface AudioSource { * @return the time offset in the sound sample when to start playing. */ public float getTimeOffset(); + + /** + * @return the current playback position of the source in seconds. + */ + public float getPlaybackTime(); /** * @return The velocity of the audio source. diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java index f7ff4c04b..598ae189c 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java @@ -54,6 +54,8 @@ public class AudioStream extends AudioData implements Closeable { protected boolean eof = false; protected int[] ids; + protected int unqueuedBuffersBytes = 0; + public AudioStream() { super(); } @@ -196,10 +198,21 @@ public class AudioStream extends AudioData implements Closeable { return in instanceof SeekableStream; } + public int getUnqueuedBufferBytes() { + return unqueuedBuffersBytes; + } + + public void setUnqueuedBufferBytes(int unqueuedBuffers) { + this.unqueuedBuffersBytes = unqueuedBuffers; + } + public void setTime(float time) { if (in instanceof SeekableStream) { ((SeekableStream) in).setTime(time); eof = false; + + // TODO: when we actually support seeking, this will need to be properly set. + unqueuedBuffersBytes = 0; } else { throw new IllegalStateException( "Cannot use setTime on a stream that " diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java index c3ccea741..62f04018a 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java @@ -301,6 +301,58 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { f.clearUpdateNeeded(); } + @Override + public float getSourcePlaybackTime(AudioSource src) { + checkDead(); + synchronized (threadLock) { + if (audioDisabled) { + return 0; + } + + // See comment in updateSourceParam(). + if (src.getChannel() < 0) { + return 0; + } + + int id = channels[src.getChannel()]; + AudioData data = src.getAudioData(); + int playbackOffsetBytes = 0; + + if (data instanceof AudioStream) { + // Because audio streams are processed in buffer chunks, + // we have to compute the amount of time the stream was already + // been playing based on the number of buffers that were processed. + AudioStream stream = (AudioStream) data; + + // NOTE: the assumption is that all enqueued buffers are the same size. + // this is currently enforced by fillBuffer(). + + // The number of unenqueued bytes that the decoder thread + // keeps track of. + int unqueuedBytes = stream.getUnqueuedBufferBytes(); + + // Additional processed buffers that the decoder thread + // did not unenqueue yet (it only updates 20 times per second). + int unqueuedBytesExtra = al.alGetSourcei(id, AL_BUFFERS_PROCESSED) * BUFFER_SIZE; + + // Total additional bytes that need to be considered. + playbackOffsetBytes = unqueuedBytes; // + unqueuedBytesExtra; + } + + // Add byte offset from source (for both streams and buffers) + playbackOffsetBytes += al.alGetSourcei(id, AL_BYTE_OFFSET); + + // Compute time value from bytes + // E.g. for 44100 source with 2 channels and 16 bits per sample: + // (44100 * 2 * 16 / 8) = 176400 + int bytesPerSecond = (data.getSampleRate() * + data.getChannels() * + data.getBitsPerSample() / 8); + + return (float)playbackOffsetBytes / bytesPerSecond; + } + } + public void updateSourceParam(AudioSource src, AudioParam param) { checkDead(); synchronized (threadLock) { @@ -648,6 +700,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean looping) { boolean success = false; int processed = al.alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); + int unqueuedBufferBytes = 0; for (int i = 0; i < processed; i++) { int buffer; @@ -656,6 +709,11 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { al.alSourceUnqueueBuffers(sourceId, 1, ib); buffer = ib.get(0); + // XXX: assume that reading from AudioStream always + // gives BUFFER_SIZE amount of bytes! This might not always + // be the case... + unqueuedBufferBytes += BUFFER_SIZE; + boolean active = fillBuffer(stream, buffer); if (!active && !stream.isEOF()) { @@ -682,6 +740,8 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { break; } } + + stream.setUnqueuedBufferBytes(stream.getUnqueuedBufferBytes() + unqueuedBufferBytes); return success; } diff --git a/jme3-core/src/main/java/com/jme3/export/JmeExporter.java b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java index 0afd170ea..b8c3abc60 100644 --- a/jme3-core/src/main/java/com/jme3/export/JmeExporter.java +++ b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java @@ -46,22 +46,18 @@ public interface JmeExporter { * * @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; + public void 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; + public void save(Savable object, File f) throws IOException; /** * Returns the {@link OutputCapsule} for the given savable object. diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index 89f82ebbd..89df1bb2b 100644 --- a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -607,28 +607,28 @@ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable } /** - * Get the color in sRGB color space as a Vector4f + * Get the color in sRGB color space as a ColorRGBA. * * Note that linear values stored in the ColorRGBA will be gamma corrected - * and returned as a Vector4f - * the x atribute will be fed with the r channel in sRGB space - * the y atribute will be fed with the g channel in sRGB space - * the z atribute will be fed with the b channel in sRGB space - * the w atribute will be fed with the a channel + * and returned as a ColorRGBA. * - * Note that no correction will be performed on the alpha channel as it's - * conventionnally doesn't represent a color itself + * The x attribute will be fed with the r channel in sRGB space. + * The y attribute will be fed with the g channel in sRGB space. + * The z attribute will be fed with the b channel in sRGB space. + * The w attribute will be fed with the a channel. * - * @return the color in sRGB color space as a Vector4f - */ - public Vector4f getAsSrgb(){ - Vector4f srgb = new Vector4f(); - float invGama = 1f/GAMMA; - srgb.x = (float)Math.pow(r, invGama); - srgb.y = (float)Math.pow(g, invGama); - srgb.z = (float)Math.pow(b, invGama); - srgb.w = a; - + * Note that no correction will be performed on the alpha channel as it + * conventionally doesn't represent a color itself. + * + * @return the color in sRGB color space as a ColorRGBA. + */ + public ColorRGBA getAsSrgb() { + ColorRGBA srgb = new ColorRGBA(); + float invGama = 1f / GAMMA; + srgb.r = (float) Math.pow(r, invGama); + srgb.g = (float) Math.pow(g, invGama); + srgb.b = (float) Math.pow(b, invGama); + srgb.a = a; return srgb; } diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 5e230dabb..d9d944057 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -902,6 +902,25 @@ final public class FastMath { return clamp(input, 0f, 1f); } + /** + * Determine if two floats are approximately equal. + * This takes into account the magnitude of the floats, since + * large numbers will have larger differences be close to each other. + * + * Should return true for a=100000, b=100001, but false for a=10000, b=10001. + * + * @param a The first float to compare + * @param b The second float to compare + * @return True if a and b are approximately equal, false otherwise. + */ + public static boolean approximateEquals(float a, float b) { + if (a == b) { + return true; + } else { + return (abs(a - b) / Math.max(abs(a), abs(b))) <= 0.00001f; + } + } + /** * Converts a single precision (32 bit) floating point value * into half precision (16 bit). diff --git a/jme3-core/src/main/java/com/jme3/math/Transform.java b/jme3-core/src/main/java/com/jme3/math/Transform.java index 79992ed2b..ac7a324d0 100644 --- a/jme3-core/src/main/java/com/jme3/math/Transform.java +++ b/jme3-core/src/main/java/com/jme3/math/Transform.java @@ -259,6 +259,26 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable return store; } + public Matrix4f toTransformMatrix() { + Matrix4f trans = new Matrix4f(); + trans.setTranslation(translation); + trans.setRotationQuaternion(rot); + trans.setScale(scale); + return trans; + } + + public void fromTransformMatrix(Matrix4f mat) { + translation.set(mat.toTranslationVector()); + rot.set(mat.toRotationQuat()); + scale.set(mat.toScaleVector()); + } + + public Transform invert() { + Transform t = new Transform(); + t.fromTransformMatrix(toTransformMatrix().invertLocal()); + return t; + } + /** * Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index f0d42c8f2..9387b687e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -337,7 +337,19 @@ public enum Caps { *

* Improves the quality of environment mapping. */ - SeamlessCubemap; + SeamlessCubemap, + + /** + * Running with OpenGL 3.2+ core profile. + * + * Compatibility features will not be available. + */ + CoreProfile, + + /** + * GPU can provide and accept binary shaders. + */ + BinaryShader; /** * Returns true if given the renderer capabilities, the texture diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index 9c73838e2..76eedb521 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java @@ -32,7 +32,6 @@ package com.jme3.renderer.opengl; import java.nio.ByteBuffer; -import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; @@ -178,6 +177,8 @@ public interface GL { public static final int GL_VERTEX_SHADER = 0x8B31; public static final int GL_ZERO = 0x0; + public void resetStats(); + public void glActiveTexture(int texture); public void glAttachShader(int program, int shader); public void glBindBuffer(int target, int buffer); diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java index 50eb065ab..190ed4547 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java @@ -35,14 +35,18 @@ import java.nio.IntBuffer; /** * GL functions only available on vanilla desktop OpenGL 3.0. - * + * * @author Kirill Vainer */ public interface GL3 extends GL2 { - + public static final int GL_DEPTH_STENCIL_ATTACHMENT = 0x821A; - public static final int GL_GEOMETRY_SHADER=0x8DD9; + public static final int GL_GEOMETRY_SHADER = 0x8DD9; + public static final int GL_NUM_EXTENSIONS = 0x821D; + public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+ public void glBindVertexArray(int param1); /// GL3+ + public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+ public void glGenVertexArrays(IntBuffer param1); /// GL3+ + public String glGetString(int param1, int param2); /// GL3+ } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java index e13402bd5..a0511f6ad 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java @@ -3,15 +3,17 @@ package com.jme3.renderer.opengl; import java.nio.ByteBuffer; import java.nio.IntBuffer; -public class GLDebugDesktop extends GLDebugES implements GL2, GL3 { +public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 { private final GL2 gl2; private final GL3 gl3; + private final GL4 gl4; - public GLDebugDesktop(GL gl, GLFbo glfbo) { - super(gl, glfbo); + public GLDebugDesktop(GL gl, GLExt glext, GLFbo glfbo) { + super(gl, glext, glfbo); this.gl2 = gl instanceof GL2 ? (GL2) gl : null; this.gl3 = gl instanceof GL3 ? (GL3) gl : null; + this.gl4 = gl instanceof GL4 ? (GL4) gl : null; } public void glAlphaFunc(int func, float ref) { @@ -73,5 +75,23 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3 { gl3.glGenVertexArrays(param1); checkError(); } + + @Override + public String glGetString(int param1, int param2) { + String result = gl3.glGetString(param1, param2); + checkError(); + return result; + } + + @Override + public void glDeleteVertexArrays(IntBuffer arrays) { + gl3.glDeleteVertexArrays(arrays); + checkError(); + } + @Override + public void glPatchParameter(int count) { + gl4.glPatchParameter(count); + checkError(); + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java index 259c56406..2348bd3cd 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java @@ -10,14 +10,16 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt { private final GLFbo glfbo; private final GLExt glext; - public GLDebugES(GL gl, GLFbo glfbo) { + public GLDebugES(GL gl, GLExt glext, GLFbo glfbo) { this.gl = gl; -// this.gl2 = gl instanceof GL2 ? (GL2) gl : null; -// this.gl3 = gl instanceof GL3 ? (GL3) gl : null; + this.glext = glext; this.glfbo = glfbo; - this.glext = glfbo instanceof GLExt ? (GLExt) glfbo : null; } + public void resetStats() { + gl.resetStats(); + } + public void glActiveTexture(int texture) { gl.glActiveTexture(texture); checkError(); @@ -478,7 +480,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt { } public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { - glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); checkError(); } @@ -525,7 +527,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt { } public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { - glext.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height); + glfbo.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height); checkError(); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java index b0e0b5f78..27f0eb8bd 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java @@ -41,7 +41,7 @@ import java.nio.IntBuffer; * * @author Kirill Vainer */ -public interface GLExt extends GLFbo { +public interface GLExt { public static final int GL_ALREADY_SIGNALED = 0x911A; public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274; @@ -100,7 +100,6 @@ public interface GLExt extends GLFbo { public static final int GL_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E; public static final int GL_WAIT_FAILED = 0x911D; - public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter); public void glBufferData(int target, IntBuffer data, int usage); public void glBufferSubData(int target, long offset, IntBuffer data); public int glClientWaitSync(Object sync, int flags, long timeout); @@ -110,7 +109,6 @@ public interface GLExt extends GLFbo { public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount); public Object glFenceSync(int condition, int flags); public void glGetMultisample(int pname, int index, FloatBuffer val); - public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height); public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations); public void glVertexAttribDivisorARB(int index, int divisor); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java index 252619db8..737019ce2 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java @@ -83,6 +83,7 @@ public interface GLFbo { public void glBindFramebufferEXT(int param1, int param2); public void glBindRenderbufferEXT(int param1, int param2); + public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter); public int glCheckFramebufferStatusEXT(int param1); public void glDeleteFramebuffersEXT(IntBuffer param1); public void glDeleteRenderbuffersEXT(IntBuffer param1); @@ -92,5 +93,5 @@ public interface GLFbo { public void glGenRenderbuffersEXT(IntBuffer param1); public void glGenerateMipmapEXT(int param1); public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4); - + public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index 423ae909e..a7ef9f52d 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -89,9 +89,11 @@ public final class GLImageFormats { GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length]; if (caps.contains(Caps.OpenGL20)) { - format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); - format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); - format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + if (!caps.contains(Caps.CoreProfile)) { + format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + } format(formatToGL, Format.RGB8, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); format(formatToGL, Format.RGB565, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5); @@ -108,8 +110,10 @@ public final class GLImageFormats { formatSrgb(formatToGL, Format.RGB565, GLExt.GL_SRGB8_EXT, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5); formatSrgb(formatToGL, Format.RGB5A1, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1); formatSrgb(formatToGL, Format.RGBA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); - formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); - formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + if (!caps.contains(Caps.CoreProfile)) { + formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); + formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + } formatSrgb(formatToGL, Format.BGR8, GLExt.GL_SRGB8_EXT, GL2.GL_BGR, GL.GL_UNSIGNED_BYTE); formatSrgb(formatToGL, Format.ABGR8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL2.GL_UNSIGNED_INT_8_8_8_8); formatSrgb(formatToGL, Format.ARGB8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8); @@ -124,16 +128,20 @@ public final class GLImageFormats { } } else if (caps.contains(Caps.Rgba8)) { // A more limited form of 32-bit RGBA. Only GL_RGBA8 is available. - format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); - format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); - format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + if (!caps.contains(Caps.CoreProfile)) { + format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + } format(formatToGL, Format.RGB8, GLExt.GL_RGBA8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } else { // Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above.. - format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); - format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); - format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + if (!caps.contains(Caps.CoreProfile)) { + format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE); + } format(formatToGL, Format.RGB8, GL.GL_RGB565, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); format(formatToGL, Format.RGBA8, GL.GL_RGBA4, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } @@ -145,9 +153,11 @@ public final class GLImageFormats { format(formatToGL, Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1); if (caps.contains(Caps.FloatTexture)) { - format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB); - format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT); - format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB); + if (!caps.contains(Caps.CoreProfile)) { + format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB); + format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT); + format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB); + } format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB); format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT); format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, GLExt.GL_HALF_FLOAT_ARB); diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 9ae1c8cc7..5e3eeca90 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -54,8 +54,10 @@ 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.MipMapGenerator; import com.jme3.util.NativeObjectManager; import java.nio.*; +import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashSet; @@ -112,13 +114,13 @@ public class GLRenderer implements Renderer { private final GLFbo glfbo; private final TextureUtil texUtil; - public GLRenderer(GL gl, GLFbo glfbo) { + public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) { this.gl = gl; this.gl2 = gl instanceof GL2 ? (GL2)gl : null; this.gl3 = gl instanceof GL3 ? (GL3)gl : null; this.gl4 = gl instanceof GL4 ? (GL4)gl : null; this.glfbo = glfbo; - this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null; + this.glext = glext; this.texUtil = new TextureUtil(gl, gl2, glext, context); } @@ -137,10 +139,19 @@ public class GLRenderer implements Renderer { return limits; } - private static HashSet loadExtensions(String extensions) { + private HashSet loadExtensions() { HashSet extensionSet = new HashSet(64); - for (String extension : extensions.split(" ")) { - extensionSet.add(extension); + if (caps.contains(Caps.OpenGL30)) { + // If OpenGL3+ is available, use the non-deprecated way + // of getting supported extensions. + gl3.glGetInteger(GL3.GL_NUM_EXTENSIONS, intBuf16); + int extensionCount = intBuf16.get(0); + for (int i = 0; i < extensionCount; i++) { + String extension = gl3.glGetString(GL.GL_EXTENSIONS, i); + extensionSet.add(extension); + } + } else { + extensionSet.addAll(Arrays.asList(gl.glGetString(GL.GL_EXTENSIONS).split(" "))); } return extensionSet; } @@ -185,10 +196,12 @@ public class GLRenderer implements Renderer { caps.add(Caps.OpenGL31); if (oglVer >= 320) { caps.add(Caps.OpenGL32); - }if(oglVer>=330){ + } + if (oglVer >= 330) { caps.add(Caps.OpenGL33); caps.add(Caps.GeometryShader); - }if(oglVer>=400){ + } + if (oglVer >= 400) { caps.add(Caps.OpenGL40); caps.add(Caps.TesselationShader); } @@ -243,7 +256,7 @@ public class GLRenderer implements Renderer { } private void loadCapabilitiesCommon() { - extensions = loadExtensions(gl.glGetString(GL.GL_EXTENSIONS)); + extensions = loadExtensions(); limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS)); if (limits.get(Limits.VertexTextureUnits) > 0) { @@ -251,7 +264,7 @@ public class GLRenderer implements Renderer { } limits.put(Limits.FragmentTextureUnits, getInteger(GL.GL_MAX_TEXTURE_IMAGE_UNITS)); - + // gl.glGetInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16); // vertexUniforms = intBuf16.get(0); // logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); @@ -279,7 +292,7 @@ public class GLRenderer implements Renderer { // == texture format extensions == - boolean hasFloatTexture = false; + boolean hasFloatTexture; hasFloatTexture = hasExtension("GL_OES_texture_half_float") && hasExtension("GL_OES_texture_float"); @@ -375,11 +388,11 @@ public class GLRenderer implements Renderer { caps.add(Caps.TextureFilterAnisotropic); } - if (hasExtension("GL_EXT_framebuffer_object")) { + if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) { caps.add(Caps.FrameBuffer); - limits.put(Limits.RenderBufferSize, getInteger(GLExt.GL_MAX_RENDERBUFFER_SIZE_EXT)); - limits.put(Limits.FrameBufferAttachments, getInteger(GLExt.GL_MAX_COLOR_ATTACHMENTS_EXT)); + limits.put(Limits.RenderBufferSize, getInteger(GLFbo.GL_MAX_RENDERBUFFER_SIZE_EXT)); + limits.put(Limits.FrameBufferAttachments, getInteger(GLFbo.GL_MAX_COLOR_ATTACHMENTS_EXT)); if (hasExtension("GL_EXT_framebuffer_blit")) { caps.add(Caps.FrameBufferBlit); @@ -434,21 +447,30 @@ public class GLRenderer implements Renderer { caps.add(Caps.SeamlessCubemap); } -// if (hasExtension("GL_ARB_get_program_binary")) { -// int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS); -// } + if (caps.contains(Caps.OpenGL32) && !hasExtension("GL_ARB_compatibility")) { + caps.add(Caps.CoreProfile); + } + + if (hasExtension("GL_ARB_get_program_binary")) { + int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS); + if (binaryFormats > 0) { + caps.add(Caps.BinaryShader); + } + } // Print context information logger.log(Level.INFO, "OpenGL Renderer Information\n" + " * Vendor: {0}\n" + " * Renderer: {1}\n" + " * OpenGL Version: {2}\n" + - " * GLSL Version: {3}", + " * GLSL Version: {3}\n" + + " * Profile: {4}", new Object[]{ gl.glGetString(GL.GL_VENDOR), gl.glGetString(GL.GL_RENDERER), gl.glGetString(GL.GL_VERSION), - gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION) + gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION), + caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility" }); // Print capabilities (if fine logging is enabled) @@ -491,6 +513,20 @@ public class GLRenderer implements Renderer { // Initialize default state.. gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + + if (caps.contains(Caps.CoreProfile)) { + // Core Profile requires VAO to be bound. + gl3.glGenVertexArrays(intBuf16); + int vaoId = intBuf16.get(0); + gl3.glBindVertexArray(vaoId); + } + if (gl2 != null) { + gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE); + if (!caps.contains(Caps.CoreProfile)) { + gl2.glEnable(GL2.GL_POINT_SPRITE); + context.pointSprite = true; + } + } } public void invalidateState() { @@ -610,31 +646,6 @@ public class GLRenderer implements Renderer { context.colorWriteEnabled = false; } - if (gl2 != null) { - 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; - } - gl2.glEnable(GL2.GL_POINT_SPRITE); - gl2.glEnable(GL2.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; - } - gl2.glDisable(GL2.GL_POINT_SPRITE); - gl2.glDisable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE); - context.pointSprite = false; - } - } - } - if (state.isPolyOffset()) { if (!context.polyOffsetEnabled) { gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); @@ -704,9 +715,6 @@ public class GLRenderer implements Renderer { 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; @@ -719,6 +727,7 @@ public class GLRenderer implements Renderer { case ModulateX2: gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR); break; + case Color: case Screen: gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); break; @@ -862,6 +871,7 @@ public class GLRenderer implements Renderer { public void postFrame() { objManager.deleteUnused(this); + gl.resetStats(); } /*********************************************************************\ @@ -1290,24 +1300,24 @@ public class GLRenderer implements Renderer { } if (src == null) { - glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, 0); + glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, 0); srcX0 = vpX; srcY0 = vpY; srcX1 = vpX + vpW; srcY1 = vpY + vpH; } else { - glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, src.getId()); + glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, src.getId()); srcX1 = src.getWidth(); srcY1 = src.getHeight(); } if (dst == null) { - glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, 0); + glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, 0); dstX0 = vpX; dstY0 = vpY; dstX1 = vpX + vpW; dstY1 = vpY + vpH; } else { - glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); + glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); dstX1 = dst.getWidth(); dstY1 = dst.getHeight(); } @@ -1315,12 +1325,12 @@ public class GLRenderer implements Renderer { if (copyDepth) { mask |= GL.GL_DEPTH_BUFFER_BIT; } - glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, + glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, GL.GL_NEAREST); - glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, prevFBO); + glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, prevFBO); } else { throw new RendererException("Framebuffer blitting not supported by the video hardware"); } @@ -1366,7 +1376,7 @@ public class GLRenderer implements Renderer { } if (context.boundRB != id) { - glfbo.glBindRenderbufferEXT(GLExt.GL_RENDERBUFFER_EXT, id); + glfbo.glBindRenderbufferEXT(GLFbo.GL_RENDERBUFFER_EXT, id); context.boundRB = id; } @@ -1384,13 +1394,13 @@ public class GLRenderer implements Renderer { if (maxSamples < samples) { samples = maxSamples; } - glext.glRenderbufferStorageMultisampleEXT(GLExt.GL_RENDERBUFFER_EXT, + glfbo.glRenderbufferStorageMultisampleEXT(GLFbo.GL_RENDERBUFFER_EXT, samples, glFmt.internalFormat, fb.getWidth(), fb.getHeight()); } else { - glfbo.glRenderbufferStorageEXT(GLExt.GL_RENDERBUFFER_EXT, + glfbo.glRenderbufferStorageEXT(GLFbo.GL_RENDERBUFFER_EXT, glFmt.internalFormat, fb.getWidth(), fb.getHeight()); @@ -1400,7 +1410,7 @@ public class GLRenderer implements Renderer { private int convertAttachmentSlot(int attachmentSlot) { // can also add support for stencil here if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { - return GLExt.GL_DEPTH_ATTACHMENT_EXT; + return GLFbo.GL_DEPTH_ATTACHMENT_EXT; } else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) { // NOTE: Using depth stencil format requires GL3, this is already // checked via render caps. @@ -1409,7 +1419,7 @@ public class GLRenderer implements Renderer { throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); } - return GLExt.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; + return GLFbo.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; } public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { @@ -1419,7 +1429,7 @@ public class GLRenderer implements Renderer { // Check NPOT requirements checkNonPowerOfTwo(tex); - updateTexImageData(image, tex.getType(), 0); + updateTexImageData(image, tex.getType(), 0, false); // NOTE: For depth textures, sets nearest/no-mips mode // Required to fix "framebuffer unsupported" @@ -1427,7 +1437,7 @@ public class GLRenderer implements Renderer { setupTextureParams(tex); } - glfbo.glFramebufferTexture2DEXT(GLExt.GL_FRAMEBUFFER_EXT, + glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT, convertAttachmentSlot(rb.getSlot()), convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), image.getId(), @@ -1445,9 +1455,9 @@ public class GLRenderer implements Renderer { updateRenderTexture(fb, rb); } if (needAttach) { - glfbo.glFramebufferRenderbufferEXT(GLExt.GL_FRAMEBUFFER_EXT, + glfbo.glFramebufferRenderbufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, convertAttachmentSlot(rb.getSlot()), - GLExt.GL_RENDERBUFFER_EXT, + GLFbo.GL_RENDERBUFFER_EXT, rb.getId()); } } @@ -1465,7 +1475,7 @@ public class GLRenderer implements Renderer { } if (context.boundFBO != id) { - glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, id); + glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, id); // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0 context.boundDrawBuf = 0; context.boundFBO = id; @@ -1545,7 +1555,7 @@ public class GLRenderer implements Renderer { if (fb == null) { // unbind any fbos if (context.boundFBO != 0) { - glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0); + glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0); statistics.onFrameBufferUse(null, true); context.boundFBO = 0; @@ -1577,7 +1587,7 @@ public class GLRenderer implements Renderer { setViewPort(0, 0, fb.getWidth(), fb.getHeight()); if (context.boundFBO != fb.getId()) { - glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, fb.getId()); + glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId()); statistics.onFrameBufferUse(fb, true); context.boundFBO = fb.getId(); @@ -1617,7 +1627,7 @@ public class GLRenderer implements Renderer { if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) { intBuf16.clear(); for (int i = 0; i < fb.getNumColorBuffers(); i++) { - intBuf16.put(GLExt.GL_COLOR_ATTACHMENT0_EXT + i); + intBuf16.put(GLFbo.GL_COLOR_ATTACHMENT0_EXT + i); } intBuf16.flip(); @@ -1629,7 +1639,7 @@ public class GLRenderer implements Renderer { // select this draw buffer if (gl2 != null) { if (context.boundDrawBuf != rb.getSlot()) { - gl2.glDrawBuffer(GLExt.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + gl2.glDrawBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); context.boundDrawBuf = rb.getSlot(); } } @@ -1658,7 +1668,7 @@ public class GLRenderer implements Renderer { setFrameBuffer(fb); if (gl2 != null) { if (context.boundReadBuf != rb.getSlot()) { - gl2.glReadBuffer(GLExt.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + gl2.glReadBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); context.boundReadBuf = rb.getSlot(); } } @@ -1682,7 +1692,7 @@ public class GLRenderer implements Renderer { public void deleteFrameBuffer(FrameBuffer fb) { if (fb.getId() != -1) { if (context.boundFBO == fb.getId()) { - glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0); + glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0); context.boundFBO = 0; } @@ -1956,8 +1966,10 @@ public class GLRenderer implements Renderer { * @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 + * @param scaleToPot If true, the image will be scaled to power-of-2 dimensions + * before being uploaded. */ - public void updateTexImageData(Image img, Texture.Type type, int unit) { + public void updateTexImageData(Image img, Texture.Type type, int unit, boolean scaleToPot) { int texId = img.getId(); if (texId == -1) { // create texture @@ -2041,33 +2053,39 @@ public class GLRenderer implements Renderer { } } + Image imageForUpload; + if (scaleToPot) { + imageForUpload = MipMapGenerator.resizeToPowerOf2(img); + } else { + imageForUpload = img; + } if (target == GL.GL_TEXTURE_CUBE_MAP) { - List data = img.getData(); + List data = imageForUpload.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++) { - texUtil.uploadTexture(img, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, linearizeSrgbImages); } } else if (target == GLExt.GL_TEXTURE_2D_ARRAY_EXT) { if (!caps.contains(Caps.TextureArray)) { throw new RendererException("Texture arrays not supported by graphics hardware"); } - List data = img.getData(); + List data = imageForUpload.getData(); // -1 index specifies prepare data for 2D Array - texUtil.uploadTexture(img, target, -1, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, target, -1, linearizeSrgbImages); for (int i = 0; i < data.size(); i++) { // upload each slice of 2D array in turn // this time with the appropriate index - texUtil.uploadTexture(img, target, i, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, target, i, linearizeSrgbImages); } } else { - texUtil.uploadTexture(img, target, 0, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, target, 0, linearizeSrgbImages); } if (img.getMultiSamples() != imageSamples) { @@ -2088,9 +2106,23 @@ public class GLRenderer implements Renderer { Image image = tex.getImage(); if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { // Check NPOT requirements - checkNonPowerOfTwo(tex); + boolean scaleToPot = false; + + try { + checkNonPowerOfTwo(tex); + } catch (RendererException ex) { + if (logger.isLoggable(Level.WARNING)) { + int nextWidth = FastMath.nearestPowerOfTwo(tex.getImage().getWidth()); + int nextHeight = FastMath.nearestPowerOfTwo(tex.getImage().getHeight()); + logger.log(Level.WARNING, + "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + + "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + + " to " + nextWidth + "x" + nextHeight); + } + scaleToPot = true; + } - updateTexImageData(image, tex.getType(), unit); + updateTexImageData(image, tex.getType(), unit, scaleToPot); } int texId = image.getId(); @@ -2620,32 +2652,13 @@ public class GLRenderer implements Renderer { 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) { - gl.glActiveTexture(GL.GL_TEXTURE0); - context.boundTextureUnit = 0; - } - if (gl2 != null) { - gl2.glDisable(GL2.GL_POINT_SPRITE); - gl2.glDisable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE); - } - context.pointSprite = false; - } - } - if (gl2 != null) { - if (context.pointSize != mesh.getPointSize()) { - gl2.glPointSize(mesh.getPointSize()); - context.pointSize = mesh.getPointSize(); - } - } if (context.lineWidth != mesh.getLineWidth()) { gl.glLineWidth(mesh.getLineWidth()); context.lineWidth = mesh.getLineWidth(); } - if(gl4!=null && mesh.getMode().equals(Mode.Patch)){ + + if (gl4 != null && mesh.getMode().equals(Mode.Patch)) { gl4.glPatchParameter(mesh.getPatchVertexCount()); } statistics.onMeshDrawn(mesh, lod, count); diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java new file mode 100644 index 000000000..7e6833b8b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.opengl; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; + +public class GLTiming implements InvocationHandler { + + private final Object obj; + private final GLTimingState state; + + public GLTiming(Object obj, GLTimingState state) { + this.obj = obj; + this.state = state; + } + + public static Object createGLTiming(Object glInterface, GLTimingState state, Class ... glInterfaceClasses) { + return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), + glInterfaceClasses, + new GLTiming(glInterface, state)); + } + + private static class CallTimingComparator implements Comparator> { + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + return (int) (o2.getValue() - o1.getValue()); + } + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + if (methodName.equals("resetStats")) { + if (state.lastPrintOutTime + 1000000000 <= System.nanoTime() && state.sampleCount > 0) { + state.timeSpentInGL /= state.sampleCount; + System.out.println("--- TOTAL TIME SPENT IN GL CALLS: " + (state.timeSpentInGL/1000) + "us"); + + Map.Entry[] callTimes = new Map.Entry[state.callTiming.size()]; + int i = 0; + for (Map.Entry callTime : state.callTiming.entrySet()) { + callTimes[i++] = callTime; + } + Arrays.sort(callTimes, new CallTimingComparator()); + int limit = 10; + for (Map.Entry callTime : callTimes) { + long val = callTime.getValue() / state.sampleCount; + String name = callTime.getKey(); + String pad = " ".substring(0, 30 - name.length()); + System.out.println("\t" + callTime.getKey() + pad + (val/1000) + "us"); + if (limit-- == 0) break; + } + for (Map.Entry callTime : callTimes) { + state.callTiming.put(callTime.getKey(), Long.valueOf(0)); + } + + state.sampleCount = 0; + state.timeSpentInGL = 0; + state.lastPrintOutTime = System.nanoTime(); + } else { + state.sampleCount++; + } + return null; + } else { + Long currentTimeObj = state.callTiming.get(methodName); + long currentTime = 0; + if (currentTimeObj != null) currentTime = currentTimeObj; + + + long startTime = System.nanoTime(); + Object result = method.invoke(obj, args); + long delta = System.nanoTime() - startTime; + + currentTime += delta; + state.timeSpentInGL += delta; + + state.callTiming.put(methodName, currentTime); + + if (delta > 1000000 && !methodName.equals("glClear")) { + // More than 1ms + // Ignore glClear as it cannot be avoided. + System.out.println("GL call " + methodName + " took " + (delta/1000) + "us to execute!"); + } + + return result; + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java new file mode 100644 index 000000000..ca25810d4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.opengl; + +import java.util.HashMap; + +public class GLTimingState { + long timeSpentInGL = 0; + int sampleCount = 0; + long lastPrintOutTime = 0; + final HashMap callTiming = new HashMap(); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java index b69d524b4..1b0d70749 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java @@ -64,6 +64,7 @@ public final class GLTracer implements InvocationHandler { noEnumArgs("glScissor", 0, 1, 2, 3); noEnumArgs("glClear", 0); noEnumArgs("glGetInteger", 1); + noEnumArgs("glGetString", 1); noEnumArgs("glBindTexture", 1); noEnumArgs("glPixelStorei", 1); @@ -95,8 +96,6 @@ public final class GLTracer implements InvocationHandler { noEnumArgs("glFramebufferTexture2DEXT", 3, 4); noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8); - - noEnumArgs("glCreateProgram", -1); noEnumArgs("glCreateShader", -1); noEnumArgs("glShaderSource", 0); @@ -155,7 +154,7 @@ public final class GLTracer implements InvocationHandler { * @return A tracer that implements the given interface */ public static Object createGlesTracer(Object glInterface, Class glInterfaceClass) { - IntMap constMap = generateConstantMap(GL.class, GLExt.class); + IntMap constMap = generateConstantMap(GL.class, GLFbo.class, GLExt.class); return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), new Class[] { glInterfaceClass }, new GLTracer(glInterface, constMap)); @@ -169,7 +168,7 @@ public final class GLTracer implements InvocationHandler { * @return A tracer that implements the given interface */ public static Object createDesktopGlTracer(Object glInterface, Class ... glInterfaceClasses) { - IntMap constMap = generateConstantMap(GL2.class, GLExt.class); + IntMap constMap = generateConstantMap(GL2.class, GL3.class, GL4.class, GLFbo.class, GLExt.class); return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), glInterfaceClasses, new GLTracer(glInterface, constMap)); diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 475cfdf71..2d603ac2c 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -583,11 +583,13 @@ public class BatchNode extends GeometryGroupNode { 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); -// } + if (inBuf == null) { + throw new IllegalArgumentException("Geometry " + geom.getName() + " has no " + outBuf.getBufferType() + " buffer whereas other geoms have. all geometries should have the same types of buffers.\n Try to use GeometryBatchFactory.alignBuffer() on the BatchNode before batching"); + } else if (outBuf == null) { + throw new IllegalArgumentException("Geometry " + geom.getName() + " has a " + outBuf.getBufferType() + " buffer whereas other geoms don't. all geometries should have the same types of buffers.\n Try to use GeometryBatchFactory.alignBuffer() on the BatchNode before batching"); + } else { + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); + } } } 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 index 35f078eeb..ab5db2462 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java @@ -149,6 +149,7 @@ public abstract class AbstractBox extends Mesh { duUpdateGeometryNormals(); duUpdateGeometryTextures(); duUpdateGeometryIndices(); + setStatic(); } /** diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java index ed53e89bc..a7a53b915 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -127,6 +127,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { unIndent(); startCondition(shaderNode.getCondition(), source); source.append(nodeSource); + source.append("\n"); endCondition(shaderNode.getCondition(), source); indent(); } diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java index 1ec695388..51c2173fd 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java @@ -172,15 +172,6 @@ public class JmeSystem { return systemDelegate.getPlatformAssetConfigURL(); } - /** - * @deprecated Directly create an image raster via {@link DefaultImageRaster}. - */ - @Deprecated - 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 diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index a3f75bd9a..150275d46 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -132,11 +132,6 @@ public abstract class JmeSystemDelegate { return new DesktopAssetManager(null); } - @Deprecated - public final ImageRaster createImageRaster(Image image, int slice) { - return new DefaultImageRaster(image, slice); - } - public abstract void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException; public abstract void showErrorDialog(String message); diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 2bbf746ff..ca9404720 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -367,7 +367,6 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { protected int width, height, depth; protected int[] mipMapSizes; protected ArrayList data; - protected transient Object efficientData; protected int multiSamples = 1; protected ColorSpace colorSpace = null; // protected int mipOffset = 0; @@ -375,7 +374,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { // attributes relating to GL object protected boolean mipsWereGenerated = false; protected boolean needGeneratedMips = false; - protected final LastTextureState lastTextureState = new LastTextureState(); + protected LastTextureState lastTextureState = new LastTextureState(); /** * Internal use only. @@ -421,7 +420,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { /** * @return True if the image needs to have mipmaps generated - * for it (as requested by the texture). + * for it (as requested by the texture). This stays true even + * after mipmaps have been generated. */ public boolean isGeneratedMipmapsRequired() { return needGeneratedMips; @@ -434,8 +434,9 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { @Override public void setUpdateNeeded() { super.setUpdateNeeded(); - if (!isGeneratedMipmapsRequired() && !hasMipmaps()) { - setNeedGeneratedMipmaps(); + if (isGeneratedMipmapsRequired() && !hasMipmaps()) { + // Mipmaps are no longer valid, since the image was changed. + setMipmapsGenerated(false); } } @@ -488,6 +489,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { Image clone = (Image) super.clone(); clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null; clone.data = data != null ? new ArrayList(data) : null; + clone.lastTextureState = new LastTextureState(); clone.setUpdateNeeded(); return clone; } @@ -527,11 +529,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { this(); - if (mipMapSizes != null && mipMapSizes.length <= 1) { - mipMapSizes = null; - } else { - needGeneratedMips = false; - mipsWereGenerated = true; + if (mipMapSizes != null) { + if (mipMapSizes.length <= 1) { + mipMapSizes = null; + } else { + needGeneratedMips = false; + mipsWereGenerated = true; + } } setFormat(format); @@ -756,8 +760,6 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { */ @Deprecated public void setEfficentData(Object efficientData){ - this.efficientData = efficientData; - setUpdateNeeded(); } /** @@ -765,7 +767,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { */ @Deprecated public Object getEfficentData(){ - return efficientData; + return null; } /** @@ -787,8 +789,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { needGeneratedMips = false; mipsWereGenerated = false; } else { - needGeneratedMips = false; - mipsWereGenerated = true; + needGeneratedMips = true; + mipsWereGenerated = false; } setUpdateNeeded(); 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 index 4cbfc3b57..c79a4675d 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java @@ -44,7 +44,9 @@ public class DefaultImageRaster extends ImageRaster { private final ImageCodec codec; private final int width; private final int height; + private final int offset; private final byte[] temp; + private final boolean convertToLinear; private int slice; private void rangeCheck(int x, int y) { @@ -53,13 +55,40 @@ public class DefaultImageRaster extends ImageRaster { } } - public DefaultImageRaster(Image image, int slice) { + public DefaultImageRaster(Image image, int slice, int mipMapLevel, boolean convertToLinear) { + int[] mipMapSizes = image.getMipMapSizes(); + int availableMips = mipMapSizes != null ? mipMapSizes.length : 1; + + if (mipMapLevel >= availableMips) { + throw new IllegalStateException("Cannot create image raster for mipmap level #" + mipMapLevel + ". " + + "Image only has " + availableMips + " mipmap levels."); + } + + if (image.hasMipmaps()) { + this.width = Math.max(1, image.getWidth() >> mipMapLevel); + this.height = Math.max(1, image.getHeight() >> mipMapLevel); + + int mipOffset = 0; + for (int i = 0; i < mipMapLevel; i++) { + mipOffset += mipMapSizes[i]; + } + + this.offset = mipOffset; + } else { + this.width = image.getWidth(); + this.height = image.getHeight(); + this.offset = 0; + } + this.image = image; this.slice = slice; + + // Conversion to linear only needed if image's color space is sRGB. + this.convertToLinear = convertToLinear && image.getColorSpace() == ColorSpace.sRGB; + 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 { @@ -86,6 +115,12 @@ public class DefaultImageRaster extends ImageRaster { public void setPixel(int x, int y, ColorRGBA color) { rangeCheck(x, y); + if (convertToLinear) { + // Input is linear, needs to be converted to sRGB before writing + // into image. + color = color.getAsSrgb(); + } + // Check flags for grayscale if (codec.isGray) { float gray = color.r * 0.27f + color.g * 0.67f + color.b * 0.06f; @@ -113,7 +148,7 @@ public class DefaultImageRaster extends ImageRaster { components[3] = Math.min( (int) (color.b * codec.maxBlue + 0.5f), codec.maxBlue); break; } - codec.writeComponents(getBuffer(), x, y, width, 0, components, temp); + codec.writeComponents(getBuffer(), x, y, width, offset, components, temp); image.setUpdateNeeded(); } @@ -128,7 +163,7 @@ public class DefaultImageRaster extends ImageRaster { public ColorRGBA getPixel(int x, int y, ColorRGBA store) { rangeCheck(x, y); - codec.readComponents(getBuffer(), x, y, width, 0, components, temp); + codec.readComponents(getBuffer(), x, y, width, offset, components, temp); if (store == null) { store = new ColorRGBA(); } @@ -169,6 +204,12 @@ public class DefaultImageRaster extends ImageRaster { store.a = 1; } } + + if (convertToLinear) { + // Input image is sRGB, need to convert to linear. + store.setAsSrgb(store.r, store.g, store.b, store.a); + } + return store; } } 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 index b4e583c35..92bbb3315 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java @@ -71,21 +71,42 @@ public abstract class ImageRaster { * @param image The image to read / write to. * @param slice Which slice to use. Only applies to 3D images, 2D image * arrays or cubemaps. + * @param mipMapLevel The mipmap level to read / write to. To access levels + * other than 0, the image must have + * {@link Image#setMipMapSizes(int[]) mipmap sizes} set. + * @param convertToLinear If true, the application expects read or written + * colors to be in linear color space (ImageRaster will + * automatically perform a conversion as needed). If false, the application expects + * colors to be in the image's native {@link Image#getColorSpace() color space}. + * @return An ImageRaster to read / write to the image. + */ + public static ImageRaster create(Image image, int slice, int mipMapLevel, boolean convertToLinear) { + return new DefaultImageRaster(image, slice, mipMapLevel, convertToLinear); + } + + /** + * 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. + * @return An ImageRaster to read / write to the image. */ public static ImageRaster create(Image image, int slice) { - return JmeSystem.createImageRaster(image, slice); + return create(image, slice, 0, false); } /** * Create new image reader / writer for 2D images. * * @param image The image to read / write to. + * @return An ImageRaster to read / write to the image. */ 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); + return create(image, 0, 0, false); } public ImageRaster() { diff --git a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java new file mode 100644 index 000000000..3ac6a7ecd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.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.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ImageRaster; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +public class MipMapGenerator { + + private MipMapGenerator() { + } + + public static Image scaleImage(Image inputImage, int outputWidth, int outputHeight) { + int size = outputWidth * outputHeight * inputImage.getFormat().getBitsPerPixel() / 8; + ByteBuffer buffer = BufferUtils.createByteBuffer(size); + Image outputImage = new Image(inputImage.getFormat(), + outputWidth, + outputHeight, + buffer, + inputImage.getColorSpace()); + + ImageRaster input = ImageRaster.create(inputImage, 0, 0, false); + ImageRaster output = ImageRaster.create(outputImage, 0, 0, false); + + float xRatio = ((float)(input.getWidth() - 1)) / output.getWidth(); + float yRatio = ((float)(input.getHeight() - 1)) / output.getHeight(); + + ColorRGBA outputColor = new ColorRGBA(); + ColorRGBA bottomLeft = new ColorRGBA(); + ColorRGBA bottomRight = new ColorRGBA(); + ColorRGBA topLeft = new ColorRGBA(); + ColorRGBA topRight = new ColorRGBA(); + + for (int y = 0; y < outputHeight; y++) { + for (int x = 0; x < outputWidth; x++) { + float x2f = x * xRatio; + float y2f = y * yRatio; + + int x2 = (int)x2f; + int y2 = (int)y2f; + + float xDiff = x2f - x2; + float yDiff = y2f - y2; + + input.getPixel(x2, y2, bottomLeft); + input.getPixel(x2 + 1, y2, bottomRight); + input.getPixel(x2, y2 + 1, topLeft); + input.getPixel(x2 + 1, y2 + 1, topRight); + + bottomLeft.multLocal( (1f - xDiff) * (1f - yDiff) ); + bottomRight.multLocal( (xDiff) * (1f - yDiff) ); + topLeft.multLocal( (1f - xDiff) * (yDiff) ); + topRight.multLocal( (xDiff) * (yDiff) ); + + outputColor.set(bottomLeft).addLocal(bottomRight) + .addLocal(topLeft).addLocal(topRight); + + output.setPixel(x, y, outputColor); + } + } + return outputImage; + } + + public static Image resizeToPowerOf2(Image original){ + int potWidth = FastMath.nearestPowerOfTwo(original.getWidth()); + int potHeight = FastMath.nearestPowerOfTwo(original.getHeight()); + return scaleImage(original, potWidth, potHeight); + } + + public static void generateMipMaps(Image image){ + int width = image.getWidth(); + int height = image.getHeight(); + + Image current = image; + ArrayList output = new ArrayList(); + int totalSize = 0; + + while (height >= 1 || width >= 1){ + output.add(current.getData(0)); + totalSize += current.getData(0).capacity(); + + if (height == 1 || width == 1) { + break; + } + + height /= 2; + width /= 2; + + current = scaleImage(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); + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag index 5d207cf83..254806d87 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -17,10 +17,7 @@ varying vec3 SpecularSum; #ifndef VERTEX_LIGHTING uniform mat4 g_ViewMatrix; uniform vec4 g_LightData[NB_LIGHTS]; - varying vec3 vPos; -#else - varying vec3 specularAccum; - varying vec4 diffuseAccum; + varying vec3 vPos; #endif #ifdef DIFFUSEMAP @@ -72,17 +69,19 @@ uniform float m_Shininess; #endif void main(){ - #ifdef NORMALMAP - mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); + #if !defined(VERTEX_LIGHTING) + #if defined(NORMALMAP) + mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); - if (!gl_FrontFacing) - { - tbnMat[2] = -tbnMat[2]; - } + if (!gl_FrontFacing) + { + tbnMat[2] = -tbnMat[2]; + } - vec3 viewDir = normalize(-vPos.xyz * tbnMat); - #else - vec3 viewDir = normalize(-vPos.xyz); + vec3 viewDir = normalize(-vPos.xyz * tbnMat); + #else + vec3 viewDir = normalize(-vPos.xyz); + #endif #endif vec2 newTexCoord; @@ -165,10 +164,9 @@ void main(){ #endif #ifdef VERTEX_LIGHTING - gl_FragColor.rgb = AmbientSum * diffuseColor.rgb - +diffuseAccum.rgb *diffuseColor.rgb - +specularAccum.rgb * specularColor.rgb; - gl_FragColor.a=1.0; + gl_FragColor.rgb = AmbientSum.rgb * diffuseColor.rgb + + DiffuseSum.rgb * diffuseColor.rgb + + SpecularSum.rgb * specularColor.rgb; #else int i = 0; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index 62b206b7e..1fde8e13d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -43,8 +43,9 @@ attribute vec3 inNormal; varying vec3 vBinormal; #endif #else - varying vec3 specularAccum; - varying vec4 diffuseAccum; + #ifdef COLORRAMP + uniform sampler2D m_ColorRamp; + #endif #endif #ifdef USE_REFLECTION @@ -128,14 +129,13 @@ void main(){ #endif #ifdef VERTEX_LIGHTING int i = 0; - diffuseAccum = vec4(0.0); - specularAccum = vec3(0.0); + vec3 diffuseAccum = vec3(0.0); + vec3 specularAccum = vec3(0.0); vec4 diffuseColor; vec3 specularColor; for (int i =0;i < NB_LIGHTS; i+=3){ vec4 lightColor = g_LightData[i]; vec4 lightData1 = g_LightData[i+1]; - DiffuseSum = vec4(1.0); #ifdef MATERIAL_COLORS diffuseColor = m_Diffuse * vec4(lightColor.rgb, 1.0); specularColor = m_Specular.rgb * lightColor.rgb; @@ -160,16 +160,19 @@ void main(){ #if __VERSION__ >= 110 } #endif - vec2 v = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess); + vec2 light = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess); #ifdef COLORRAMP - diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor; + diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor.rgb; specularAccum += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor; #else - diffuseAccum += v.x * diffuseColor; - specularAccum += v.y * specularColor; + diffuseAccum += light.x * diffuseColor.rgb; + specularAccum += light.y * specularColor; #endif } + + DiffuseSum.rgb *= diffuseAccum.rgb; + SpecularSum.rgb *= specularAccum.rgb; #endif diff --git a/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg b/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg index df72654a1..28727d070 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg @@ -2,26 +2,4 @@ INCLUDE com/jme3/asset/General.cfg # Desktop-specific loaders LOADER com.jme3.texture.plugins.AWTLoader : jpg, bmp, gif, png, jpeg -LOADER com.jme3.audio.plugins.OGGLoader : oggLOADER 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,geom,tsctrl,tseval, glsl, glsllib -LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx -LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba diff --git a/jme3-core/src/main/resources/com/jme3/asset/General.cfg b/jme3-core/src/main/resources/com/jme3/asset/General.cfg index c0098ffe5..c56b62146 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/General.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/General.cfg @@ -21,6 +21,6 @@ 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 +LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index 5f29e4105..d7ce2f50a 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/jme3-core/src/main/resources/joystick-mapping.properties @@ -6,7 +6,7 @@ # the new name as it will be reported through the Joystick # interface. # -# Keys with spaces in them should have those spaces escaped. +# Keys with spaces in them should have those spaces escaped. # Values do not need their spaces escaped. For example: # # Some\ Joystick.0=3 @@ -37,22 +37,29 @@ 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 + +# XBOX 360 Controller connected to Android using +# the USB dongle +Xbox\ 360\ Wireless\ Receiver.AXIS_RX=z +Xbox\ 360\ Wireless\ Receiver.AXIS_RY=rz +Xbox\ 360\ Wireless\ Receiver.z=AXIS_RX +Xbox\ 360\ Wireless\ Receiver.rz=AXIS_RY 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 index c02f7088a..eb12bf908 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java @@ -31,6 +31,7 @@ */ package com.jme3.export.binary; +import com.jme3.asset.AssetManager; import com.jme3.export.FormatVersion; import com.jme3.export.JmeExporter; import com.jme3.export.Savable; @@ -167,8 +168,33 @@ public class BinaryExporter implements JmeExporter { public static BinaryExporter getInstance() { return new BinaryExporter(); } + + /** + * Saves the object into memory then loads it from memory. + * + * Used by tests to check if the persistence system is working. + * + * @param The type of savable. + * @param assetManager AssetManager to load assets from. + * @param object The object to save and then load. + * @return A new instance that has been saved and loaded from the + * original object. + */ + public static T saveAndLoad(AssetManager assetManager, T object) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + BinaryExporter exporter = new BinaryExporter(); + exporter.save(object, baos); + BinaryImporter importer = new BinaryImporter(); + importer.setAssetManager(assetManager); + return (T) importer.load(baos.toByteArray()); + } catch (IOException ex) { + // Should never happen. + throw new AssertionError(ex); + } + } - public boolean save(Savable object, OutputStream os) throws IOException { + public void save(Savable object, OutputStream os) throws IOException { // reset some vars aliasCount = 1; idCount = 1; @@ -286,7 +312,7 @@ public class BinaryExporter implements JmeExporter { out = null; os = null; - if (debug ) { + if (debug) { logger.fine("Stats:"); logger.log(Level.FINE, "classes: {0}", classNum); logger.log(Level.FINE, "class table: {0} bytes", classTableSize); @@ -294,8 +320,6 @@ public class BinaryExporter implements JmeExporter { logger.log(Level.FINE, "location table: {0} bytes", locationTableSize); logger.log(Level.FINE, "data: {0} bytes", location); } - - return true; } protected String getChunk(BinaryIdContentPair pair) { @@ -325,7 +349,7 @@ public class BinaryExporter implements JmeExporter { return bytes; } - public boolean save(Savable object, File f) throws IOException { + public void save(Savable object, File f) throws IOException { File parentDirectory = f.getParentFile(); if (parentDirectory != null && !parentDirectory.exists()) { parentDirectory.mkdirs(); @@ -333,11 +357,9 @@ public class BinaryExporter implements JmeExporter { FileOutputStream fos = new FileOutputStream(f); try { - return save(object, fos); + save(object, fos); } finally { - if (fos != null) { - fos.close(); - } + fos.close(); } } diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 90b0b7f1f..361fb65e1 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -582,10 +582,15 @@ public class J3MLoader implements AssetLoader { public Object load(AssetInfo info) throws IOException { this.assetManager = info.getManager(); - + InputStream in = info.openStream(); try { - key = info.getKey(); + key = info.getKey(); + if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) { + throw new IOException("Material instances must be loaded via MaterialKey"); + } else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) { + throw new IOException("Material definitions must be loaded via AssetKey"); + } loadFromRoot(BlockLanguageParser.parse(in)); } finally { if (in != null){ @@ -594,9 +599,6 @@ public class J3MLoader implements AssetLoader { } if (material != null){ - if (!(info.getKey() instanceof MaterialKey)){ - throw new IOException("Material instances must be loaded via MaterialKey"); - } // material implementation return material; }else{ 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 index 9c71aa132..726cf3e19 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java @@ -242,21 +242,12 @@ public class DXTFlipper { 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; + flipDXT3Block(alphaBlock, h); + break; case 3: case 4: flipDXT5Block(alphaBlock, h); @@ -264,6 +255,16 @@ public class DXTFlipper { } retImg.put(alphaBlock); } + + 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); } retImg.rewind(); }else if (h >= 4){ diff --git a/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java index a7668dc56..9904b3283 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java @@ -16,6 +16,7 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.*; +import java.util.logging.Level; import java.util.logging.Logger; public class GeometryBatchFactory { @@ -453,4 +454,93 @@ public class GeometryBatchFactory { mergeGeometries(geoms, outMesh); printMesh(outMesh); } + + /** + * Options to align the buffers of geometries' meshes of a sub graph + * + */ + public static enum AlignOption { + + /** + * Will remove the buffers of a type that is not on all the geometries + */ + RemoveUnalignedBuffers, + /** + * Will create missing buffers and pad with dummy data + */ + CreateMissingBuffers + } + + /** + * Will ensure that all the geometries' meshes of the n sub graph have the + * same types of buffers + * @param n the node to gather geometries from + * @param option the align options + * @see AlignOption + * + * Very experimental for now. + */ + public static void alignBuffers(Node n, AlignOption option) { + List geoms = new ArrayList(); + gatherGeoms(n, geoms); + + //gather buffer types + Map types = new EnumMap(VertexBuffer.Type.class); + Map typesCount = new EnumMap(VertexBuffer.Type.class); + for (Geometry geom : geoms) { + for (VertexBuffer buffer : geom.getMesh().getBufferList()) { + if (types.get(buffer.getBufferType()) == null) { + types.put(buffer.getBufferType(), buffer); + logger.log(Level.FINE, buffer.getBufferType().toString()); + } + Integer count = typesCount.get(buffer.getBufferType()); + if (count == null) { + count = 0; + } + count++; + typesCount.put(buffer.getBufferType(), count); + } + } + + switch (option) { + case RemoveUnalignedBuffers: + for (Geometry geom : geoms) { + + for (VertexBuffer buffer : geom.getMesh().getBufferList()) { + Integer count = typesCount.get(buffer.getBufferType()); + if (count != null && count < geoms.size()) { + geom.getMesh().clearBuffer(buffer.getBufferType()); + logger.log(Level.FINE, "removing {0} from {1}", new Object[]{buffer.getBufferType(), geom.getName()}); + + } + } + } + break; + case CreateMissingBuffers: + for (Geometry geom : geoms) { + for (VertexBuffer.Type type : types.keySet()) { + if (geom.getMesh().getBuffer(type) == null) { + VertexBuffer vb = new VertexBuffer(type); + Buffer b; + switch (type) { + case Index: + case BoneIndex: + case HWBoneIndex: + b = BufferUtils.createIntBuffer(geom.getMesh().getVertexCount() * types.get(type).getNumComponents()); + break; + case InterleavedData: + b = BufferUtils.createByteBuffer(geom.getMesh().getVertexCount() * types.get(type).getNumComponents()); + break; + default: + b = BufferUtils.createFloatBuffer(geom.getMesh().getVertexCount() * types.get(type).getNumComponents()); + } + vb.setupData(types.get(type).getUsage(), types.get(type).getNumComponents(), types.get(type).getFormat(), b); + geom.getMesh().setBuffer(vb); + logger.log(Level.FINE, "geom {0} misses buffer {1}. Creating", new Object[]{geom.getName(), type}); + } + } + } + break; + } + } } 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 index 75d6b1c86..7e9012b6d 100644 --- a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java @@ -162,8 +162,8 @@ public class SSAOFilter extends Filter { }; 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); +// 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()); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java index a4b4b92a3..8f9447ea1 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java @@ -69,7 +69,7 @@ public class TestAttachDriver extends SimpleApplication implements ActionListene 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(); @@ -79,7 +79,7 @@ public class TestAttachDriver extends SimpleApplication implements ActionListene public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); setupKeys(); setupFloor(); buildPlayer(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java index 5b6992f3a..5fac3fc5f 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java @@ -85,7 +85,7 @@ public class TestAttachGhostObject extends SimpleApplication implements AnalogLi public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); setupKeys(); setupJoint(); } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java index beb57bdec..cda07bc85 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java @@ -1,152 +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); - } - } -} +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE 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.setDebugEnabled(true); + 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 index 0bfa9f784..410c125d2 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java @@ -61,8 +61,8 @@ public class TestCollisionGroups extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); - + bulletAppState.setDebugEnabled(true); + // 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)); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java index 1a13b09fb..3c9450f3d 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java @@ -1,98 +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!"); - } - } - } - -} +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE 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.setDebugEnabled(true); + 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 index b2c44ce4e..2128149d2 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java @@ -67,7 +67,7 @@ public class TestCollisionShapeFactory extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); createMaterial(); Node node = new Node("node1"); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java index dde9e530c..74a7c1e1e 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java @@ -62,7 +62,7 @@ public class TestGhostObject extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Mesh to be shared across several boxes. Box boxGeom = new Box(Vector3f.ZERO, 1f, 1f, 1f); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java index 636122b4b..e63cd03e5 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java @@ -60,7 +60,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // 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)); @@ -69,7 +69,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { //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); + getPhysicsSpace().add(physicsSphere); //Making it not kinematic again, it should fall under gravity, it doesn't physicsSphere.getControl(RigidBodyControl.class).setKinematic(false); @@ -77,7 +77,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { 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 @@ -85,7 +85,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { //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); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java b/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java index ed432da47..3835b1ca0 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java @@ -59,7 +59,7 @@ public class TestLocalPhysics extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Add a physics sphere to the world Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java index 26d7c1a8e..4ba4e060b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java @@ -69,7 +69,7 @@ public class TestPhysicsCar extends SimpleApplication implements ActionListener public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); setupKeys(); buildPlayer(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java index f01ad23f9..029892ad9 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java @@ -76,7 +76,7 @@ public class TestPhysicsHingeJoint extends SimpleApplication implements AnalogLi public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); setupKeys(); setupJoint(); } @@ -102,7 +102,7 @@ public class TestPhysicsHingeJoint extends SimpleApplication implements AnalogLi @Override public void simpleUpdate(float tpf) { - + } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java index c51afd3cc..7a1ea5187 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java @@ -1,5 +1,5 @@ package jme3test.bullet; - + import com.jme3.app.SimpleApplication; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.collision.PhysicsCollisionObject; @@ -16,30 +16,30 @@ 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); + bulletAppState.setDebugEnabled(true); } - + @Override public void simpleUpdate(float tpf) { List rayTest = bulletAppState.getPhysicsSpace().rayTest(cam.getLocation(), cam.getLocation().add(cam.getDirection())); @@ -50,7 +50,7 @@ public class TestPhysicsRayCast extends SimpleApplication { fpsText.setText(collisionObject.getUserObject().toString()); } } - + private void initCrossHair() { BitmapText bitmapText = new BitmapText(guiFont); bitmapText.setText("+"); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java index 5724f7a16..7d94e0570 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java @@ -69,7 +69,7 @@ public class TestPhysicsReadWrite extends SimpleApplication{ public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); physicsRootNode=new Node("PhysicsRootNode"); rootNode.attachChild(physicsRootNode); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java index 5768bc021..2752ce5a8 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java @@ -64,7 +64,7 @@ public class TestRagDoll extends SimpleApplication implements ActionListener { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); inputManager.addMapping("Pull ragdoll up", new MouseButtonTrigger(0)); inputManager.addListener(this, "Pull ragdoll up"); PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java b/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java index 2045e5c77..9cf2808ae 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java @@ -59,7 +59,7 @@ public class TestSimplePhysics extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Add a physics sphere to the world Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java index 0613e5d22..da9089cec 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java @@ -35,7 +35,7 @@ public class TestSweepTest extends SimpleApplication { public void simpleInitApp() { obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f); capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f); - + stateManager.attach(bulletAppState); capsule = new Node("capsule"); @@ -52,7 +52,7 @@ public class TestSweepTest extends SimpleApplication { bulletAppState.getPhysicsSpace().add(obstacle); rootNode.attachChild(obstacle); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); } @Override @@ -70,7 +70,7 @@ public class TestSweepTest extends SimpleApplication { colliding = true; } } - + if (!colliding) { // if the sweep is clear then move the spatial capsule.move(move, 0, 0); diff --git a/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java b/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java index 552287f75..d245de23f 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java @@ -69,26 +69,9 @@ public class TestParticleExportingCloning extends SimpleApplication { 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); + ParticleEmitter emit3 = BinaryExporter.saveAndLoad(assetManager, emit); + emit3.move(-3, 0, 0); + rootNode.attachChild(emit3); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java b/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java new file mode 100644 index 000000000..de162907d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE 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.export.binary.BinaryExporter; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +public class TestModelExportingCloning extends SimpleApplication { + + public static void main(String[] args) { + TestModelExportingCloning app = new TestModelExportingCloning(); + app.start(); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(10f, 3f, 40f)); + cam.lookAtDirection(Vector3f.UNIT_Z.negate(), 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); + + AnimControl control; + AnimChannel channel; + + Spatial originalModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + control = originalModel.getControl(AnimControl.class); + channel = control.createChannel(); + channel.setAnim("Walk"); + rootNode.attachChild(originalModel); + + Spatial clonedModel = originalModel.clone(); + clonedModel.move(10, 0, 0); + control = clonedModel.getControl(AnimControl.class); + channel = control.createChannel(); + channel.setAnim("push"); + rootNode.attachChild(clonedModel); + + Spatial exportedModel = BinaryExporter.saveAndLoad(assetManager, originalModel); + exportedModel.move(20, 0, 0); + control = exportedModel.getControl(AnimControl.class); + channel = control.createChannel(); + channel.setAnim("pull"); + rootNode.attachChild(exportedModel); + } +} diff --git a/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag index 4cd92cff9..355cf6092 100644 --- a/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag +++ b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag @@ -1,5 +1,5 @@ #extension GL_EXT_texture_array : enable -#extension GL_EXT_gpu_shader4 : enable +// #extension GL_EXT_gpu_shader4 : enable uniform vec4 m_Color; @@ -8,7 +8,7 @@ uniform vec4 m_Color; #endif #ifdef HAS_COLORMAP - #if !defined(GL_EXT_texture_array) && !defined(GL_EXT_gpu_shader4) + #if !defined(GL_EXT_texture_array) #error Texture arrays are not supported, but required for this shader. #endif diff --git a/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg index 715eab985..e9d79a459 100644 --- a/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg +++ b/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg @@ -2,3 +2,9 @@ INCLUDE com/jme3/asset/General.cfg # IOS specific loaders LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg +LOADER com.jme3.audio.plugins.OGGLoader : ogg +LOADER com.jme3.material.plugins.J3MLoader : j3m +LOADER com.jme3.material.plugins.J3MLoader : j3md +LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib +LOADER com.jme3.export.binary.BinaryImporter : j3o +LOADER com.jme3.font.plugins.BitmapFontLoader : fnt diff --git a/jme3-ios/src/main/java/com/jme3/audio/android/AL.java b/jme3-ios/src/main/java/com/jme3/audio/android/AL.java deleted file mode 100644 index d8fea3933..000000000 --- a/jme3-ios/src/main/java/com/jme3/audio/android/AL.java +++ /dev/null @@ -1,1054 +0,0 @@ -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-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java b/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java deleted file mode 100644 index e7f4a0f98..000000000 --- a/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java +++ /dev/null @@ -1,67 +0,0 @@ -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-ios/src/main/java/com/jme3/audio/ios/IosAL.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java new file mode 100644 index 000000000..7812dc748 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java @@ -0,0 +1,53 @@ +package com.jme3.audio.ios; + +import com.jme3.audio.openal.AL; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +public final class IosAL implements AL { + + public IosAL() { + } + + public native String alGetString(int parameter); + + public native int alGenSources(); + + public native int alGetError(); + + public native void alDeleteSources(int numSources, IntBuffer sources); + + public native void alGenBuffers(int numBuffers, IntBuffer buffers); + + public native void alDeleteBuffers(int numBuffers, IntBuffer buffers); + + public native void alSourceStop(int source); + + public native void alSourcei(int source, int param, int value); + + public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency); + + public native void alSourcePlay(int source); + + public native void alSourcePause(int source); + + public native void alSourcef(int source, int param, float value); + + public native void alSource3f(int source, int param, float value1, float value2, float value3); + + public native int alGetSourcei(int source, int param); + + public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers); + + public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers); + + public native void alListener(int param, FloatBuffer data); + + public native void alListenerf(int param, float value); + + public native void alListener3f(int param, float value1, float value2, float value3); + + public native void alSource3i(int source, int param, int value1, int value2, int value3); + +} diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java new file mode 100644 index 000000000..f1579c9e5 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java @@ -0,0 +1,26 @@ +package com.jme3.audio.ios; + +import com.jme3.audio.openal.ALC; +import java.nio.IntBuffer; + +public final class IosALC implements ALC { + + public IosALC() { + } + + public native void createALC(); + + public native void destroyALC(); + + public native boolean isCreated(); + + public native String alcGetString(int parameter); + + public native boolean alcIsExtensionPresent(String extension); + + public native void alcGetInteger(int param, IntBuffer buffer, int size); + + public native void alcDevicePauseSOFT(); + + public native void alcDeviceResumeSOFT(); +} diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java new file mode 100644 index 000000000..d7a569c1f --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java @@ -0,0 +1,32 @@ +package com.jme3.audio.ios; + +import com.jme3.audio.openal.EFX; +import java.nio.IntBuffer; + +public class IosEFX implements EFX { + + public IosEFX() { + } + + public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers); + + public native void alGenEffects(int numEffects, IntBuffer buffers); + + public native void alEffecti(int effect, int param, int value); + + public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value); + + public native void alDeleteEffects(int numEffects, IntBuffer buffers); + + public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers); + + public native void alGenFilters(int numFilters, IntBuffer buffers); + + public native void alFilteri(int filter, int param, int value); + + public native void alFilterf(int filter, int param, float value); + + public native void alDeleteFilters(int numFilters, IntBuffer buffers); + + public native void alEffectf(int effect, int param, float value); +} diff --git a/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java b/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java deleted file mode 100644 index a11425b23..000000000 --- a/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jme3.audio.plugins; - -import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetLoader; -import com.jme3.audio.android.AndroidAudioData; -import java.io.IOException; - -/** - * AndroidAudioLoader 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-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java deleted file mode 100644 index 8a59d0be8..000000000 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java +++ /dev/null @@ -1,2573 +0,0 @@ -package com.jme3.renderer.ios; - -import com.jme3.light.LightList; -import com.jme3.material.RenderState; -import com.jme3.math.*; -import com.jme3.renderer.*; -import com.jme3.scene.Mesh; -import com.jme3.scene.Mesh.Mode; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.shader.Attribute; -import com.jme3.shader.Shader; -import com.jme3.shader.Shader.ShaderSource; -import com.jme3.shader.Shader.ShaderType; -import com.jme3.shader.Uniform; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.FrameBuffer.RenderBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.WrapAxis; -import com.jme3.util.BufferUtils; -import com.jme3.util.ListMap; -import com.jme3.util.NativeObjectManager; - -import java.nio.*; -import java.util.EnumSet; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3tools.shader.ShaderDebug; - -/** - * The Renderer is responsible for taking rendering commands and - * executing them on the underlying video hardware. - * - * @author Kirill Vainer - */ -public class IGLESShaderRenderer implements Renderer { - - private static final Logger logger = Logger.getLogger(IGLESShaderRenderer.class.getName()); - private static final boolean VALIDATE_SHADER = false; - - private final NativeObjectManager objManager = new NativeObjectManager(); - private final EnumSet caps = EnumSet.noneOf(Caps.class); - private final Statistics statistics = new Statistics(); - private final StringBuilder stringBuf = new StringBuilder(250); - private final RenderContext context = new RenderContext(); - private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); - - 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 final int[] intBuf1 = new int[1]; - private final int[] intBuf16 = new int[16]; - - private int glslVer; - private int vertexTextureUnits; - private int fragTextureUnits; - private int vertexUniforms; - private int fragUniforms; - private int vertexAttribs; - private int maxRBSize; - private int maxTexSize; - private int maxCubeTexSize; - - private FrameBuffer lastFb = null; - private FrameBuffer mainFbOverride = null; - private boolean useVBO = true; - private boolean powerVr = false; - private boolean uintIndexSupport = false; - - private Shader boundShader; - - private int vpX, vpY, vpW, vpH; - private int clipX, clipY, clipW, clipH; - - public IGLESShaderRenderer() { - logger.log(Level.FINE, "IGLESShaderRenderer Constructor"); - } - - /** - * Get the capabilities of the renderer. - * @return The capabilities of the renderer. - */ - public EnumSet getCaps() { - logger.log(Level.FINE, "IGLESShaderRenderer getCaps"); - return caps; - } - - /** - * 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() { - logger.log(Level.FINE, "IGLESShaderRenderer getStatistics"); - return statistics; - } - - /** - * Invalidates the current rendering state. Should be called after - * the GL state was changed manually or through an external library. - */ - public void invalidateState() { - logger.log(Level.FINE, "IGLESShaderRenderer 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) { - logger.log(Level.FINE, "IGLESShaderRenderer clearBuffers"); - 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) { - JmeIosGLES.glColorMask(true, true, true, true); - context.colorWriteEnabled = true; - } - bits = JmeIosGLES.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) { - JmeIosGLES.glDepthMask(true); - context.depthWriteEnabled = true; - } - bits |= JmeIosGLES.GL_DEPTH_BUFFER_BIT; - } - if (stencil) { - bits |= JmeIosGLES.GL_STENCIL_BUFFER_BIT; - } - if (bits != 0) { - JmeIosGLES.glClear(bits); - JmeIosGLES.checkGLError(); - } - } - - /** - * Sets the background (aka clear) color. - * - * @param color The background color to set - */ - public void setBackgroundColor(ColorRGBA color) { - logger.log(Level.FINE, "IGLESShaderRenderer setBackgroundColor"); - JmeIosGLES.glClearColor(color.r, color.g, color.b, color.a); - JmeIosGLES.checkGLError(); - } - - /** - * Applies the given {@link RenderState}, making the necessary - * GL calls so that the state is applied. - */ - public void applyRenderState(RenderState state) { - logger.log(Level.FINE, "IGLESShaderRenderer applyRenderState"); - /* - 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) { - JmeIosGLES.glEnable(JmeIosGLES.GL_DEPTH_TEST); - JmeIosGLES.glDepthFunc(convertTestFunction(context.depthFunc)); - JmeIosGLES.checkGLError(); - context.depthTestEnabled = true; - } else if (!state.isDepthTest() && context.depthTestEnabled) { - JmeIosGLES.glDisable(JmeIosGLES.GL_DEPTH_TEST); - JmeIosGLES.checkGLError(); - context.depthTestEnabled = false; - } - if (state.getDepthFunc() != context.depthFunc) { - JmeIosGLES.glDepthFunc(convertTestFunction(state.getDepthFunc())); - context.depthFunc = state.getDepthFunc(); - } - - if (state.isDepthWrite() && !context.depthWriteEnabled) { - JmeIosGLES.glDepthMask(true); - JmeIosGLES.checkGLError(); - context.depthWriteEnabled = true; - } else if (!state.isDepthWrite() && context.depthWriteEnabled) { - JmeIosGLES.glDepthMask(false); - JmeIosGLES.checkGLError(); - context.depthWriteEnabled = false; - } - if (state.isColorWrite() && !context.colorWriteEnabled) { - JmeIosGLES.glColorMask(true, true, true, true); - JmeIosGLES.checkGLError(); - context.colorWriteEnabled = true; - } else if (!state.isColorWrite() && context.colorWriteEnabled) { - JmeIosGLES.glColorMask(false, false, false, false); - JmeIosGLES.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) { - JmeIosGLES.glEnable(JmeIosGLES.GL_POLYGON_OFFSET_FILL); - JmeIosGLES.glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - JmeIosGLES.checkGLError(); - - context.polyOffsetEnabled = true; - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } else { - if (state.getPolyOffsetFactor() != context.polyOffsetFactor - || state.getPolyOffsetUnits() != context.polyOffsetUnits) { - JmeIosGLES.glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - JmeIosGLES.checkGLError(); - - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } - } - } else { - if (context.polyOffsetEnabled) { - JmeIosGLES.glDisable(JmeIosGLES.GL_POLYGON_OFFSET_FILL); - JmeIosGLES.checkGLError(); - - context.polyOffsetEnabled = false; - context.polyOffsetFactor = 0; - context.polyOffsetUnits = 0; - } - } - if (state.getFaceCullMode() != context.cullMode) { - if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { - JmeIosGLES.glDisable(JmeIosGLES.GL_CULL_FACE); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glEnable(JmeIosGLES.GL_CULL_FACE); - JmeIosGLES.checkGLError(); - } - - switch (state.getFaceCullMode()) { - case Off: - break; - case Back: - JmeIosGLES.glCullFace(JmeIosGLES.GL_BACK); - JmeIosGLES.checkGLError(); - break; - case Front: - JmeIosGLES.glCullFace(JmeIosGLES.GL_FRONT); - JmeIosGLES.checkGLError(); - break; - case FrontAndBack: - JmeIosGLES.glCullFace(JmeIosGLES.GL_FRONT_AND_BACK); - JmeIosGLES.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) { - JmeIosGLES.glDisable(JmeIosGLES.GL_BLEND); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glEnable(JmeIosGLES.GL_BLEND); - switch (state.getBlendMode()) { - case Off: - break; - case Additive: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE); - break; - case AlphaAdditive: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_SRC_ALPHA, JmeIosGLES.GL_ONE); - break; - case Color: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE_MINUS_SRC_COLOR); - break; - case Alpha: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_SRC_ALPHA, JmeIosGLES.GL_ONE_MINUS_SRC_ALPHA); - break; - case PremultAlpha: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE_MINUS_SRC_ALPHA); - break; - case Modulate: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_DST_COLOR, JmeIosGLES.GL_ZERO); - break; - case ModulateX2: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_DST_COLOR, JmeIosGLES.GL_SRC_COLOR); - break; - default: - throw new UnsupportedOperationException("Unrecognized blend mode: " - + state.getBlendMode()); - } - JmeIosGLES.checkGLError(); - } - context.blendMode = state.getBlendMode(); - } - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setDepthRange"); - JmeIosGLES.glDepthRangef(start, end); - JmeIosGLES.checkGLError(); - } - - /** - * Called when a new frame has been rendered. - */ - public void postFrame() { - logger.log(Level.FINE, "IGLESShaderRenderer onFrame"); - //JmeIosGLES.checkGLErrorForced(); - JmeIosGLES.checkGLError(); - - objManager.deleteUnused(this); - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setWorldMatrix"); - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setViewProjectionMatrices"); - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setViewPort"); - if (x != vpX || vpY != y || vpW != width || vpH != height) { - JmeIosGLES.glViewport(x, y, width, height); - JmeIosGLES.checkGLError(); - - vpX = x; - vpY = y; - vpW = width; - vpH = 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setClipRect"); - if (!context.clipRectEnabled) { - JmeIosGLES.glEnable(JmeIosGLES.GL_SCISSOR_TEST); - JmeIosGLES.checkGLError(); - context.clipRectEnabled = true; - } - if (clipX != x || clipY != y || clipW != width || clipH != height) { - JmeIosGLES.glScissor(x, y, width, height); - JmeIosGLES.checkGLError(); - clipX = x; - clipY = y; - clipW = width; - clipH = height; - } - } - - /** - * Clears the clipping rectangle set with - * {@link #setClipRect(int, int, int, int) }. - */ - public void clearClipRect() { - logger.log(Level.FINE, "IGLESShaderRenderer clearClipRect"); - if (context.clipRectEnabled) { - JmeIosGLES.glDisable(JmeIosGLES.GL_SCISSOR_TEST); - JmeIosGLES.checkGLError(); - context.clipRectEnabled = false; - - clipX = 0; - clipY = 0; - clipW = 0; - clipH = 0; - } - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setLighting"); - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setShader"); - 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); - } - } - - /** - * Deletes a shader. This method also deletes - * the attached shader sources. - * - * @param shader Shader to delete. - */ - public void deleteShader(Shader shader) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteShader"); - if (shader.getId() == -1) { - logger.warning("Shader is not uploaded to GPU, cannot delete."); - return; - } - - for (ShaderSource source : shader.getSources()) { - if (source.getId() != -1) { - JmeIosGLES.glDetachShader(shader.getId(), source.getId()); - JmeIosGLES.checkGLError(); - - deleteShaderSource(source); - } - } - - JmeIosGLES.glDeleteProgram(shader.getId()); - JmeIosGLES.checkGLError(); - - statistics.onDeleteShader(); - shader.resetObject(); - } - - /** - * Deletes the provided shader source. - * - * @param source The ShaderSource to delete. - */ - public void deleteShaderSource(ShaderSource source) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteShaderSource"); - if (source.getId() < 0) { - logger.warning("Shader source is not uploaded to GPU, cannot delete."); - return; - } - - source.clearUpdateNeeded(); - - JmeIosGLES.glDeleteShader(source.getId()); - JmeIosGLES.checkGLError(); - - source.resetObject(); - } - - /** - * Copies contents from src to dst, scaling if necessary. - */ - public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { - logger.log(Level.FINE, "IGLESShaderRenderer copyFrameBuffer"); - copyFrameBuffer(src, dst, true); - } - - /** - * 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) { - logger.warning("IGLESShaderRenderer copyFrameBuffer with depth TODO"); - throw new RendererException("Copy framebuffer not implemented yet."); - } - - /** - * Sets the framebuffer that will be drawn to. - */ - public void setFrameBuffer(FrameBuffer fb) { - logger.log(Level.FINE, "IGLESShaderRenderer setFrameBuffer"); - 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()); - JmeIosGLES.glGenerateMipmap(textureType); - JmeIosGLES.checkGLError(); - } - } - } - - if (fb == null) { - // unbind any fbos - if (context.boundFBO != 0) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, 0); - JmeIosGLES.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()) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, fb.getId()); - JmeIosGLES.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 < 16; i++) { - intBuf16[i] = i < fb.getNumColorBuffers() ? JmeIosGLES.GL_COLOR_ATTACHMENT0 + i : 0; - } - - //intBuf16.flip();// TODO: flip -// glDrawBuffers(intBuf16); - context.boundDrawBuf = 100 + fb.getNumColorBuffers(); - } - } else { - RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); - // select this draw buffer - if (context.boundDrawBuf != rb.getSlot()) { - JmeIosGLES.glActiveTexture(convertAttachmentSlot(rb.getSlot())); - JmeIosGLES.checkGLError(); - - context.boundDrawBuf = rb.getSlot(); - } - } - } - - assert fb.getId() >= 0; - assert context.boundFBO == fb.getId(); - - lastFb = fb; - - checkFrameBufferStatus(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) { - logger.log(Level.FINE, "IGLESShaderRenderer setMainFrameBufferOverride"); - mainFbOverride = 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) { - logger.log(Level.FINE, "IGLESShaderRenderer readFrameBuffer"); - 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); - } - - //JmeIosGLES.glReadPixels2(vpX, vpY, vpW, vpH, JmeIosGLES.GL_RGBA, JmeIosGLES.GL_UNSIGNED_BYTE, byteBuf.array(), 0, vpW * vpH * 4); - JmeIosGLES.glReadPixels(vpX, vpY, vpW, vpH, JmeIosGLES.GL_RGBA, JmeIosGLES.GL_UNSIGNED_BYTE, byteBuf); - JmeIosGLES.checkGLError(); - } - - /** - * Deletes a framebuffer and all attached renderbuffers - */ - public void deleteFrameBuffer(FrameBuffer fb) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteFrameBuffer"); - if (fb.getId() != -1) { - if (context.boundFBO == fb.getId()) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, 0); - JmeIosGLES.checkGLError(); - - context.boundFBO = 0; - } - - if (fb.getDepthBuffer() != null) { - deleteRenderBuffer(fb, fb.getDepthBuffer()); - } - if (fb.getColorBuffer() != null) { - deleteRenderBuffer(fb, fb.getColorBuffer()); - } - - intBuf1[0] = fb.getId(); - JmeIosGLES.glDeleteFramebuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - fb.resetObject(); - - statistics.onDeleteFrameBuffer(); - } - } - - /** - * Sets the texture to use for the given texture unit. - */ - public void setTexture(int unit, Texture tex) { - logger.log(Level.FINE, "IGLESShaderRenderer setTexture"); - 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 (textures[unit] != image) { - if (context.boundTextureUnit != unit) { - JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0 + unit); - context.boundTextureUnit = unit; - } - - JmeIosGLES.glBindTexture(type, texId); - JmeIosGLES.checkGLError(); - - textures[unit] = image; - - statistics.onTextureUse(tex.getImage(), true); - } else { - statistics.onTextureUse(tex.getImage(), false); - } - - setupTextureParams(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) { - logger.log(Level.FINE, "IGLESShaderRenderer modifyTexture"); - setTexture(0, tex); - TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); - } - - /** - * Deletes a texture from the GPU. - */ - public void deleteImage(Image image) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteImage"); - int texId = image.getId(); - if (texId != -1) { - intBuf1[0] = texId; - - JmeIosGLES.glDeleteTextures(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - image.resetObject(); - - statistics.onDeleteTexture(); - } - } - - /** - * Uploads a vertex buffer to the GPU. - * - * @param vb The vertex buffer to upload - */ - public void updateBufferData(VertexBuffer vb) { - logger.log(Level.FINE, "IGLESShaderRenderer updateBufferData"); - int bufId = vb.getId(); - boolean created = false; - if (bufId == -1) { - // create buffer - JmeIosGLES.glGenBuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - bufId = intBuf1[0]; - vb.setId(bufId); - objManager.registerObject(vb); - - created = true; - } - - // bind buffer - int target; - if (vb.getBufferType() == VertexBuffer.Type.Index) { - target = JmeIosGLES.GL_ELEMENT_ARRAY_BUFFER; - if (context.boundElementArrayVBO != bufId) { - JmeIosGLES.glBindBuffer(target, bufId); - JmeIosGLES.checkGLError(); - - context.boundElementArrayVBO = bufId; - } - } else { - target = JmeIosGLES.GL_ARRAY_BUFFER; - if (context.boundArrayVBO != bufId) { - JmeIosGLES.glBindBuffer(target, bufId); - JmeIosGLES.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: - JmeIosGLES.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - case Short: - case UnsignedShort: - JmeIosGLES.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - case Int: - case UnsignedInt: - JmeIosGLES.glBufferData(target, size, (IntBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - case Float: - JmeIosGLES.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); - JmeIosGLES.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: - JmeIosGLES.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - case Short: - case UnsignedShort: - JmeIosGLES.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - case Int: - case UnsignedInt: - JmeIosGLES.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - case Float: - JmeIosGLES.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - default: - throw new RuntimeException("Unknown buffer format."); - } - } - vb.clearUpdateNeeded(); - } - - /** - * Deletes a vertex buffer from the GPU. - * @param vb The vertex buffer to delete - */ - public void deleteBuffer(VertexBuffer vb) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteBuffer"); - int bufId = vb.getId(); - if (bufId != -1) { - // delete buffer - intBuf1[0] = bufId; - - JmeIosGLES.glDeleteBuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - vb.resetObject(); - } - } - - /** - * 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, VertexBuffer[] instanceData) { - logger.log(Level.FINE, "IGLESShaderRenderer renderMesh"); - 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()) { - JmeIosGLES.glLineWidth(mesh.getLineWidth()); - JmeIosGLES.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); - } - } - - /** - * 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() { - logger.log(Level.FINE, "IGLESShaderRenderer resetGLObjects"); - objManager.resetObjects(); - statistics.clearMemory(); - boundShader = null; - lastFb = null; - context.reset(); - } - - /** - * 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() { - logger.log(Level.FINE, "IGLESShaderRenderer cleanup"); - objManager.deleteAllObjects(this); - statistics.clearMemory(); - } - - /** - * 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) { - logger.log(Level.FINE, "IGLESShaderRenderer setAlphaToCoverage"); - if (value) { - JmeIosGLES.glEnable(JmeIosGLES.GL_SAMPLE_ALPHA_TO_COVERAGE); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glDisable(JmeIosGLES.GL_SAMPLE_ALPHA_TO_COVERAGE); - JmeIosGLES.checkGLError(); - } - } - - - /* ------------------------------------------------------------------------------ */ - - - public void initialize() { - Level store = logger.getLevel(); - logger.setLevel(Level.FINE); - - logger.log(Level.FINE, "Vendor: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_VENDOR)); - logger.log(Level.FINE, "Renderer: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_RENDERER)); - logger.log(Level.FINE, "Version: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_VERSION)); - logger.log(Level.FINE, "Shading Language Version: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_SHADING_LANGUAGE_VERSION)); - - /* - // 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 ", JmeIosGLES.glGetString(JmeIosGLES.GL_VERSION)); - if (openGlVer == -1) { - glslVer = -1; - throw new UnsupportedOperationException("OpenGL ES 2.0+ is required for IGLESShaderRenderer!"); - } - - // Check shader language version - glslVer = extractVersion("OpenGL ES GLSL ES ", JmeIosGLES.glGetString(JmeIosGLES.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; - } - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16, 0); - vertexTextureUnits = intBuf16[0]; - logger.log(Level.FINE, "VTF Units: {0}", vertexTextureUnits); - if (vertexTextureUnits > 0) { - caps.add(Caps.VertexTextureFetch); - } - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16, 0); - fragTextureUnits = intBuf16[0]; - logger.log(Level.FINE, "Texture Units: {0}", fragTextureUnits); - - // Multiply vector count by 4 to get float count. - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_UNIFORM_VECTORS, intBuf16, 0); - vertexUniforms = intBuf16[0] * 4; - logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_FRAGMENT_UNIFORM_VECTORS, intBuf16, 0); - fragUniforms = intBuf16[0] * 4; - logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VARYING_VECTORS, intBuf16, 0); - int varyingFloats = intBuf16[0] * 4; - logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_ATTRIBS, intBuf16, 0); - vertexAttribs = intBuf16[0]; - logger.log(Level.FINE, "Vertex Attributes: {0}", vertexAttribs); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_SUBPIXEL_BITS, intBuf16, 0); - int subpixelBits = intBuf16[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); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_TEXTURE_SIZE, intBuf16, 0); - maxTexSize = intBuf16[0]; - logger.log(Level.FINE, "Maximum Texture Resolution: {0}", maxTexSize); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16, 0); - maxCubeTexSize = intBuf16[0]; - logger.log(Level.FINE, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_RENDERBUFFER_SIZE, intBuf16, 0); - maxRBSize = intBuf16[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 = JmeIosGLES.glGetString(JmeIosGLES.GL_EXTENSIONS); - logger.log(Level.FINE, "GL_EXTENSIONS: {0}", extensions); - - // Get number of compressed formats available. - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_NUM_COMPRESSED_TEXTURE_FORMATS, intBuf16, 0); - int numCompressedFormats = intBuf16[0]; - - // Allocate buffer for compressed formats. - int[] compressedFormats = new int[numCompressedFormats]; - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats, 0); - - // Check for errors after all glGet calls. - JmeIosGLES.checkGLError(); - - // Print compressed formats. - for (int i = 0; i < numCompressedFormats; i++) { - logger.log(Level.FINE, "Compressed Texture Formats: {0}", compressedFormats[i]); - } - - TextureUtil.loadTextureFeatures(extensions); - - applyRenderState(RenderState.DEFAULT); - JmeIosGLES.glDisable(JmeIosGLES.GL_DITHER); - JmeIosGLES.checkGLError(); - - logger.log(Level.FINE, "Caps: {0}", caps); - logger.setLevel(store); - - uintIndexSupport = extensions.contains("GL_OES_element_index_uint"); - logger.log(Level.FINE, "Support for UInt index: {0}", uintIndexSupport); - } - - - /* ------------------------------------------------------------------------------ */ - - - 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(); - } - float version = Float.parseFloat(versionStr); - return (int) (version * 100); - } else { - return -1; - } - } - - private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - intBuf1[0] = rb.getId(); - JmeIosGLES.glDeleteRenderbuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - } - - private int convertUsage(Usage usage) { - switch (usage) { - case Static: - return JmeIosGLES.GL_STATIC_DRAW; - case Dynamic: - return JmeIosGLES.GL_DYNAMIC_DRAW; - case Stream: - return JmeIosGLES.GL_STREAM_DRAW; - default: - throw new RuntimeException("Unknown usage type."); - } - } - - - protected void bindProgram(Shader shader) { - int shaderId = shader.getId(); - if (context.boundShaderProgram != shaderId) { - JmeIosGLES.glUseProgram(shaderId); - JmeIosGLES.checkGLError(); - - statistics.onShaderUse(shader, true); - boundShader = shader; - context.boundShaderProgram = shaderId; - } else { - statistics.onShaderUse(shader, false); - } - } - - 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 updateUniform(Shader shader, Uniform uniform) { - logger.log(Level.FINE, "IGLESShaderRenderer private updateUniform: " + uniform.getVarType()); - int shaderId = shader.getId(); - - assert uniform.getName() != null; - assert shader.getId() > 0; - - if (context.boundShaderProgram != shaderId) { - JmeIosGLES.glUseProgram(shaderId); - JmeIosGLES.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(); - ByteBuffer bb;//GetPrimitiveArrayCritical - FloatBuffer fb; - IntBuffer ib; - switch (uniform.getVarType()) { - case Float: - Float f = (Float) uniform.getValue(); - JmeIosGLES.glUniform1f(loc, f.floatValue()); - break; - case Vector2: - Vector2f v2 = (Vector2f) uniform.getValue(); - JmeIosGLES.glUniform2f(loc, v2.getX(), v2.getY()); - break; - case Vector3: - Vector3f v3 = (Vector3f) uniform.getValue(); - JmeIosGLES.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); - break; - case Vector4: - Object val = uniform.getValue(); - if (val instanceof ColorRGBA) { - ColorRGBA c = (ColorRGBA) val; - JmeIosGLES.glUniform4f(loc, c.r, c.g, c.b, c.a); - } else if (val instanceof Vector4f) { - Vector4f c = (Vector4f) val; - JmeIosGLES.glUniform4f(loc, c.x, c.y, c.z, c.w); - } else { - Quaternion c = (Quaternion) uniform.getValue(); - JmeIosGLES.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); - } - break; - case Boolean: - Boolean b = (Boolean) uniform.getValue(); - JmeIosGLES.glUniform1i(loc, b.booleanValue() ? JmeIosGLES.GL_TRUE : JmeIosGLES.GL_FALSE); - break; - case Matrix3: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 9; - JmeIosGLES.glUniformMatrix3fv(loc, 1, false, fb); - break; - case Matrix4: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 16; - JmeIosGLES.glUniformMatrix4fv(loc, 1, false, fb); - break; - case IntArray: - ib = (IntBuffer) uniform.getValue(); - JmeIosGLES.glUniform1iv(loc, ib.limit(), ib); - break; - case FloatArray: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform1fv(loc, fb.limit(), fb); - break; - case Vector2Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform2fv(loc, fb.limit() / 2, fb); - break; - case Vector3Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform3fv(loc, fb.limit() / 3, fb); - break; - case Vector4Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform4fv(loc, fb.limit() / 4, fb); - break; - case Matrix4Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniformMatrix4fv(loc, fb.limit() / 16, false, fb); - break; - case Int: - Integer i = (Integer) uniform.getValue(); - JmeIosGLES.glUniform1i(loc, i.intValue()); - break; - default: - throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); - } - JmeIosGLES.checkGLError(); - } - - protected void updateUniformLocation(Shader shader, Uniform uniform) { - stringBuf.setLength(0); - stringBuf.append(uniform.getName()).append('\0'); - updateNameBuffer(); - int loc = JmeIosGLES.glGetUniformLocation(shader.getId(), uniform.getName()); - JmeIosGLES.checkGLError(); - - if (loc < 0) { - uniform.setLocation(-1); - // uniform is not declared in shader - } else { - uniform.setLocation(loc); - } - } - - 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 void updateShaderData(Shader shader) { - int id = shader.getId(); - boolean needRegister = false; - if (id == -1) { - // create program - id = JmeIosGLES.glCreateProgram(); - JmeIosGLES.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); - } - - JmeIosGLES.glAttachShader(id, source.getId()); - JmeIosGLES.checkGLError(); - } - - // link shaders to program - JmeIosGLES.glLinkProgram(id); - JmeIosGLES.checkGLError(); - - JmeIosGLES.glGetProgramiv(id, JmeIosGLES.GL_LINK_STATUS, intBuf1, 0); - JmeIosGLES.checkGLError(); - - boolean linkOK = intBuf1[0] == JmeIosGLES.GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !linkOK) { - JmeIosGLES.glGetProgramiv(id, JmeIosGLES.GL_INFO_LOG_LENGTH, intBuf1, 0); - JmeIosGLES.checkGLError(); - - int length = intBuf1[0]; - if (length > 3) { - // get infos - infoLog = JmeIosGLES.glGetProgramInfoLog(id); - JmeIosGLES.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 + "\n" + infoLog); - } else { - throw new RendererException("Shader link failure, shader:" + shader + "\ninfo: "); - } - } - } - - 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 - } - } - - public void updateTexImageData(Image img, Texture.Type type) { - int texId = img.getId(); - if (texId == -1) { - // create texture - JmeIosGLES.glGenTextures(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - texId = intBuf1[0]; - img.setId(texId); - objManager.registerObject(img); - - statistics.onNewTexture(); - } - - // bind texture - int target = convertTextureType(type); - if (context.boundTextures[0] != img) { - if (context.boundTextureUnit != 0) { - JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0); - JmeIosGLES.checkGLError(); - - context.boundTextureUnit = 0; - } - - JmeIosGLES.glBindTexture(target, texId); - JmeIosGLES.checkGLError(); - - context.boundTextures[0] = img; - } - - boolean needMips = false; - if (img.isGeneratedMipmapsRequired()) { - needMips = true; - img.setMipmapsGenerated(true); - } - - if (target == JmeIosGLES.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 == JmeIosGLES.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(JmeIosGLES.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, JmeIosGLES.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(); - } - - private void setupTextureParams(Texture tex) { - int target = convertTextureType(tex.getType()); - - // filter things - int minFilter = convertMinFilter(tex.getMinFilter()); - int magFilter = convertMagFilter(tex.getMagFilter()); - - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_MIN_FILTER, minFilter); - JmeIosGLES.checkGLError(); - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_MAG_FILTER, magFilter); - JmeIosGLES.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: - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); - - // fall down here is intentional.. -// case OneDimensional: - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); - - JmeIosGLES.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); - } - } - */ - } - - private int convertTextureType(Texture.Type type) { - switch (type) { - case TwoDimensional: - return JmeIosGLES.GL_TEXTURE_2D; - // case TwoDimensionalArray: - // return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT; -// case ThreeDimensional: - // return GLES20.GL_TEXTURE_3D; - case CubeMap: - return JmeIosGLES.GL_TEXTURE_CUBE_MAP; - default: - throw new UnsupportedOperationException("Unknown texture type: " + type); - } - } - - private int convertMagFilter(Texture.MagFilter filter) { - switch (filter) { - case Bilinear: - return JmeIosGLES.GL_LINEAR; - case Nearest: - return JmeIosGLES.GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown mag filter: " + filter); - } - } - - private int convertMinFilter(Texture.MinFilter filter) { - switch (filter) { - case Trilinear: - return JmeIosGLES.GL_LINEAR_MIPMAP_LINEAR; - case BilinearNearestMipMap: - return JmeIosGLES.GL_LINEAR_MIPMAP_NEAREST; - case NearestLinearMipMap: - return JmeIosGLES.GL_NEAREST_MIPMAP_LINEAR; - case NearestNearestMipMap: - return JmeIosGLES.GL_NEAREST_MIPMAP_NEAREST; - case BilinearNoMipMaps: - return JmeIosGLES.GL_LINEAR; - case NearestNoMipMaps: - return JmeIosGLES.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 JmeIosGLES.GL_CLAMP_TO_EDGE; - case Repeat: - return JmeIosGLES.GL_REPEAT; - case MirroredRepeat: - return JmeIosGLES.GL_MIRRORED_REPEAT; - default: - throw new UnsupportedOperationException("Unknown wrap mode: " + mode); - } - } - - 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 { - JmeIosGLES.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); - JmeIosGLES.checkGLError(); - } - clearVertexAttribs(); - } - - 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"); - JmeIosGLES.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); - JmeIosGLES.checkGLError(); - } - clearVertexAttribs(); - } - - - 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 = JmeIosGLES.glGetAttribLocation(programId, attributeName); - JmeIosGLES.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)) { - JmeIosGLES.glEnableVertexAttribArray(loc); - JmeIosGLES.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) { - JmeIosGLES.glBindBuffer(JmeIosGLES.GL_ARRAY_BUFFER, bufId); - JmeIosGLES.checkGLError(); - - context.boundArrayVBO = bufId; - } - - vb.getData().rewind(); - /* - Android22Workaround.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - 0); - */ - logger.warning("iTODO Android22Workaround"); - - JmeIosGLES.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - null); - - JmeIosGLES.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{*/ - JmeIosGLES.glDrawArrays(convertElementMode(mode), 0, vertCount); - JmeIosGLES.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) { - JmeIosGLES.glBindBuffer(JmeIosGLES.GL_ELEMENT_ARRAY_BUFFER, bufId); - JmeIosGLES.checkGLError(); - - context.boundElementArrayVBO = bufId; - } - - int vertCount = mesh.getVertexCount(); - boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - - Buffer indexData = indexBuf.getData(); - - if (!uintIndexSupport && (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); - JmeIosGLES.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); - JmeIosGLES.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 { - logger.log(Level.FINE, "IGLESShaderRenderer drawTriangleList TODO check"); - indexData.rewind(); - JmeIosGLES.glDrawElementsIndex( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - 0); - /*TODO: - indexData.rewind(); - JmeIosGLES.glDrawElements( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - 0); - */ - JmeIosGLES.checkGLError(); - } - } - } - - public int convertElementMode(Mesh.Mode mode) { - switch (mode) { - case Points: - return JmeIosGLES.GL_POINTS; - case Lines: - return JmeIosGLES.GL_LINES; - case LineLoop: - return JmeIosGLES.GL_LINE_LOOP; - case LineStrip: - return JmeIosGLES.GL_LINE_STRIP; - case Triangles: - return JmeIosGLES.GL_TRIANGLES; - case TriangleFan: - return JmeIosGLES.GL_TRIANGLE_FAN; - case TriangleStrip: - return JmeIosGLES.GL_TRIANGLE_STRIP; - default: - throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); - } - } - - - private int convertVertexBufferFormat(Format format) { - switch (format) { - case Byte: - return JmeIosGLES.GL_BYTE; - case UnsignedByte: - return JmeIosGLES.GL_UNSIGNED_BYTE; - case Short: - return JmeIosGLES.GL_SHORT; - case UnsignedShort: - return JmeIosGLES.GL_UNSIGNED_SHORT; - case Int: - return JmeIosGLES.GL_INT; - case UnsignedInt: - return JmeIosGLES.GL_UNSIGNED_INT; - /* - case Half: - return NVHalfFloat.GL_HALF_FLOAT_NV; - // return ARBHalfFloatVertex.GL_HALF_FLOAT; - */ - case Float: - return JmeIosGLES.GL_FLOAT; -// case Double: -// return JmeIosGLES.GL_DOUBLE; - default: - throw new RuntimeException("Unknown buffer format."); - - } - } - - public void clearVertexAttribs() { - IDList attribList = context.attribIndexList; - for (int i = 0; i < attribList.oldLen; i++) { - int idx = attribList.oldList[i]; - - JmeIosGLES.glDisableVertexAttribArray(idx); - JmeIosGLES.checkGLError(); - - context.boundAttribs[idx] = null; - } - context.attribIndexList.copyNewToOld(); - } - - public void updateFrameBuffer(FrameBuffer fb) { - int id = fb.getId(); - if (id == -1) { - // create FBO - JmeIosGLES.glGenFramebuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - id = intBuf1[0]; - fb.setId(id); - objManager.registerObject(fb); - - statistics.onNewFrameBuffer(); - } - - if (context.boundFBO != id) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, id); - JmeIosGLES.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 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) { - JmeIosGLES.glFramebufferRenderbuffer(JmeIosGLES.GL_FRAMEBUFFER, - convertAttachmentSlot(rb.getSlot()), - JmeIosGLES.GL_RENDERBUFFER, - rb.getId()); - - JmeIosGLES.checkGLError(); - } - } - - - 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); - } - - JmeIosGLES.glFramebufferTexture2D(JmeIosGLES.GL_FRAMEBUFFER, - convertAttachmentSlot(rb.getSlot()), - convertTextureType(tex.getType()), - image.getId(), - 0); - - JmeIosGLES.checkGLError(); - } - - - private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - int id = rb.getId(); - if (id == -1) { - JmeIosGLES.glGenRenderbuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - id = intBuf1[0]; - rb.setId(id); - } - - if (context.boundRB != id) { - JmeIosGLES.glBindRenderbuffer(JmeIosGLES.GL_RENDERBUFFER, id); - JmeIosGLES.checkGLError(); - - context.boundRB = id; - } - - if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { - throw new RendererException("Resolution " + fb.getWidth() - + ":" + fb.getHeight() + " is not supported."); - } - - TextureUtil.IosGLImageFormat 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 { - JmeIosGLES.glRenderbufferStorage(JmeIosGLES.GL_RENDERBUFFER, - imageFormat.renderBufferStorageFormat, - fb.getWidth(), - fb.getHeight()); - - JmeIosGLES.checkGLError(); - } - } - - - private int convertAttachmentSlot(int attachmentSlot) { - // can also add support for stencil here - if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { - return JmeIosGLES.GL_DEPTH_ATTACHMENT; - } else if (attachmentSlot == 0) { - return JmeIosGLES.GL_COLOR_ATTACHMENT0; - } else { - throw new UnsupportedOperationException("Android does not support multiple color attachments to an FBO"); - } - } - - - 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 = JmeIosGLES.glCheckFramebufferStatus(JmeIosGLES.GL_FRAMEBUFFER); - switch (status) { - case JmeIosGLES.GL_FRAMEBUFFER_COMPLETE: - break; - case JmeIosGLES.GL_FRAMEBUFFER_UNSUPPORTED: - //Choose different formats - throw new IllegalStateException("Framebuffer object format is " - + "unsupported by the video hardware."); - case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - throw new IllegalStateException("Framebuffer has erronous attachment."); - case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); - case JmeIosGLES.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? " + JmeIosGLES.glIsRenderbuffer(rb.getId())); - - int attachment = convertAttachmentSlot(rb.getSlot()); - - //intBuf16.clear(); - JmeIosGLES.glGetFramebufferAttachmentParameteriv(JmeIosGLES.GL_FRAMEBUFFER, - attachment, JmeIosGLES.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, intBuf16, 0); - int type = intBuf16[0]; - - //intBuf16.clear(); - JmeIosGLES.glGetFramebufferAttachmentParameteriv(JmeIosGLES.GL_FRAMEBUFFER, - attachment, JmeIosGLES.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, intBuf16, 0); - int rbName = intBuf16[0]; - - switch (type) { - case JmeIosGLES.GL_NONE: - System.out.println("Type: None"); - break; - case JmeIosGLES.GL_TEXTURE: - System.out.println("Type: Texture"); - break; - case JmeIosGLES.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? " + JmeIosGLES.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) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.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); - } - - - } - - 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); - JmeIosGLES.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); - JmeIosGLES.checkGLError(); - - curOffset += elementLength * elSize; - } - } else { - JmeIosGLES.glDrawElements( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - indexBuf.getData()); - JmeIosGLES.checkGLError(); - } - } - - 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 = JmeIosGLES.glGetAttribLocation(programId, attributeName); - JmeIosGLES.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 - JmeIosGLES.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - avb.getData()); - - JmeIosGLES.checkGLError(); - - JmeIosGLES.glEnableVertexAttribArray(loc); - JmeIosGLES.checkGLError(); - - attribs[loc] = vb; - } // if (attribs[loc] != vb) - } else { - throw new IllegalStateException("Cannot render mesh without shader bound"); - } - } - - public void setVertexAttrib_Array(VertexBuffer vb) { - setVertexAttrib_Array(vb, null); - } - - - public void updateShaderSourceData(ShaderSource source) { - int id = source.getId(); - if (id == -1) { - // Create id - id = JmeIosGLES.glCreateShader(convertShaderType(source.getType())); - JmeIosGLES.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. - JmeIosGLES.glShaderSource( - id, source.getDefines() - + source.getSource()); - } else { - String precision =""; - if (source.getType() == ShaderType.Fragment) { - precision = "precision mediump float;\n"; - } - JmeIosGLES.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]); - - JmeIosGLES.glCompileShader(id); - JmeIosGLES.checkGLError(); - - JmeIosGLES.glGetShaderiv(id, JmeIosGLES.GL_COMPILE_STATUS, intBuf1, 0); - JmeIosGLES.checkGLError(); - - boolean compiledOK = intBuf1[0] == JmeIosGLES.GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !compiledOK) { - // even if compile succeeded, check - // log for warnings - JmeIosGLES.glGetShaderiv(id, JmeIosGLES.GL_INFO_LOG_LENGTH, intBuf1, 0); - JmeIosGLES.checkGLError(); - infoLog = JmeIosGLES.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(stringBuf.toString() + source.getDefines() + source.getSource())}); - if (infoLog != null) { - throw new RendererException("compile error in: " + source + "\n" + infoLog); - } else { - throw new RendererException("compile error in: " + source + "\nerror: "); - } - } - } - - - public int convertShaderType(ShaderType type) { - switch (type) { - case Fragment: - return JmeIosGLES.GL_FRAGMENT_SHADER; - case Vertex: - return JmeIosGLES.GL_VERTEX_SHADER; -// case Geometry: -// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; - default: - throw new RuntimeException("Unrecognized shader type."); - } - } - - private int convertTestFunction(RenderState.TestFunction testFunc) { - switch (testFunc) { - case Never: - return JmeIosGLES.GL_NEVER; - case Less: - return JmeIosGLES.GL_LESS; - case LessOrEqual: - return JmeIosGLES.GL_LEQUAL; - case Greater: - return JmeIosGLES.GL_GREATER; - case GreaterOrEqual: - return JmeIosGLES.GL_GEQUAL; - case Equal: - return JmeIosGLES.GL_EQUAL; - case NotEqual: - return JmeIosGLES.GL_NOTEQUAL; - case Always: - return JmeIosGLES.GL_ALWAYS; - default: - throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); - } - } - - public void setMainFrameBufferSrgb(boolean srgb) { - - } - - public void setLinearizeSrgbImages(boolean linearize) { - - } - - public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { - throw new UnsupportedOperationException("Not supported yet. URA will make that work seamlessly"); - } -} \ No newline at end of file diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java index d3276972d..a9398f159 100644 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java @@ -34,6 +34,7 @@ package com.jme3.renderer.ios; import com.jme3.renderer.RendererException; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GLExt; +import com.jme3.renderer.opengl.GLFbo; import java.nio.Buffer; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; @@ -46,10 +47,13 @@ import java.nio.ShortBuffer; * * @author Kirill Vainer */ -public class IosGL implements GL, GLExt { +public class IosGL implements GL, GLExt, GLFbo { private final int[] temp_array = new int[16]; + public void resetStats() { + } + private static int getLimitBytes(ByteBuffer buffer) { checkLimit(buffer); return buffer.limit(); @@ -90,7 +94,9 @@ public class IosGL implements GL, GLExt { if (buffer.remaining() < n) { throw new BufferOverflowException(); } + int pos = buffer.position(); buffer.put(array, 0, n); + buffer.position(pos); } private static void checkLimit(Buffer buffer) { diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java deleted file mode 100644 index d11308c2a..000000000 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java +++ /dev/null @@ -1,576 +0,0 @@ -package com.jme3.renderer.ios; - -//import android.graphics.Bitmap; -//import android.opengl.ETC1; -//import android.opengl.ETC1Util.ETC1Texture; -//import android.opengl.JmeIosGLES; -//import android.opengl.GLUtils; -//import com.jme3.asset.AndroidImageInfo; -import com.jme3.renderer.ios.JmeIosGLES; -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 PVRTC = 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"); - - PVRTC = extensionString.contains("GL_IMG_texture_compression_pvrtc"); - - 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 PVRTC? {0}", PVRTC); - 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"); - - JmeIosGLES.glPixelStorei(JmeIosGLES.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(JmeIosGLES.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(JmeIosGLES.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); - JmeIosGLES.checkGLError(); - } else { - GLUtils.texImage2D(target, level, bitmap, 0); - JmeIosGLES.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) { - JmeIosGLES.glCompressedTexSubImage2D(target, - level, - x, y, - bitmap.getWidth(), - bitmap.getHeight(), - ETC1.ETC1_RGB8_OES, - etc1tex.getData().capacity(), - etc1tex.getData()); - - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glCompressedTexImage2D(target, - level, - ETC1.ETC1_RGB8_OES, - bitmap.getWidth(), - bitmap.getHeight(), - 0, - etc1tex.getData().capacity(), - etc1tex.getData()); - - JmeIosGLES.checkGLError(); - } - -// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB, -// JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5, etc1Texture); -// } else if (bytesPerPixel == 3) { -// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB, -// JmeIosGLES.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); - JmeIosGLES.checkGLError(); - } else { - GLUtils.texImage2D(target, 0, bitmap, 0); - JmeIosGLES.checkGLError(); - } - - if (needMips) { - // No pregenerated mips available, - // generate from base level if required - JmeIosGLES.glGenerateMipmap(target); - JmeIosGLES.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 - JmeIosGLES.glGenerateMipmap(target); - JmeIosGLES.checkGLError(); - } - //} - } - - private static void unsupportedFormat(Format fmt) { - throw new UnsupportedOperationException("The image format '" + fmt + "' is unsupported by the video hardware."); - } - - public static IosGLImageFormat getImageFormat(Format fmt) throws UnsupportedOperationException { - IosGLImageFormat imageFormat = new IosGLImageFormat(); - switch (fmt) { - case Depth32: - case Depth32F: - throw new UnsupportedOperationException("The image format '" - + fmt + "' is not supported by OpenGL ES 2.0 specification."); - case Alpha8: - imageFormat.format = JmeIosGLES.GL_ALPHA; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - if (RGBA8) { - imageFormat.renderBufferStorageFormat = GL_RGBA8; - } else { - // Highest precision alpha supported by vanilla OGLES2 - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; - } - break; - case Luminance8: - imageFormat.format = JmeIosGLES.GL_LUMINANCE; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - if (RGBA8) { - imageFormat.renderBufferStorageFormat = GL_RGBA8; - } else { - // Highest precision luminance supported by vanilla OGLES2 - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; - } - break; - case Luminance8Alpha8: - imageFormat.format = JmeIosGLES.GL_LUMINANCE_ALPHA; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - if (RGBA8) { - imageFormat.renderBufferStorageFormat = GL_RGBA8; - } else { - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; - } - break; - case RGB565: - imageFormat.format = JmeIosGLES.GL_RGB; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5; - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; - break; - case RGB5A1: - imageFormat.format = JmeIosGLES.GL_RGBA; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_5_5_1; - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB5_A1; - break; - case RGB8: - imageFormat.format = JmeIosGLES.GL_RGB; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - if (RGBA8) { - imageFormat.renderBufferStorageFormat = GL_RGBA8; - } else { - // Fallback: Use RGB565 if RGBA8 is not available. - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; - } - break; - case BGR8: - imageFormat.format = JmeIosGLES.GL_RGB; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - if (RGBA8) { - imageFormat.renderBufferStorageFormat = GL_RGBA8; - } else { - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; - } - break; - case RGBA8: - imageFormat.format = JmeIosGLES.GL_RGBA; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - if (RGBA8) { - imageFormat.renderBufferStorageFormat = GL_RGBA8; - } else { - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; - } - break; - case Depth: - case Depth16: - if (!DEPTH_TEXTURE) { - unsupportedFormat(fmt); - } - imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT; - imageFormat.renderBufferStorageFormat = JmeIosGLES.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 = JmeIosGLES.GL_DEPTH_COMPONENT; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT; - imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16; - } - break; - case DXT1: - if (!DXT1) { - unsupportedFormat(fmt); - } - imageFormat.format = GL_DXT1; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - imageFormat.compress = true; - break; - case DXT1A: - if (!DXT1) { - unsupportedFormat(fmt); - } - imageFormat.format = GL_DXT1A; - imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; - imageFormat.compress = true; - break; - default: - throw new UnsupportedOperationException("Unrecognized format: " + fmt); - } - return imageFormat; - } - - public static class IosGLImageFormat { - - 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 && img.isNPOT()) { - // Check if texture is POT - throw new RendererException("Non-power-of-2 textures " - + "are not supported by the video hardware " - + "and no scaling path available for image: " + img); - } - IosGLImageFormat imageFormat = getImageFormat(fmt); - - if (data != null) { - JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); - JmeIosGLES.checkGLError(); - } - - 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) { - JmeIosGLES.glCompressedTexImage2D(target, - i, - imageFormat.format, - mipWidth, - mipHeight, - 0, - data.remaining(), - data); - } else { - JmeIosGLES.glTexImage2D(target, - i, - imageFormat.format, - mipWidth, - mipHeight, - 0, - imageFormat.format, - imageFormat.dataType, - data); - } - JmeIosGLES.checkGLError(); - - 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) { - //TODO: - /* - 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 && img.isNPOT()) { - // Check if texture is POT - throw new RendererException("Non-power-of-2 textures " - + "are not supported by the video hardware " - + "and no scaling path available for image: " + img); - } - IosGLImageFormat imageFormat = getImageFormat(fmt); - - if (data != null) { - JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); - JmeIosGLES.checkGLError(); - } - - 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) { - JmeIosGLES.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data); - JmeIosGLES.checkGLError(); - } - - pos += mipSizes[i]; - } - } -} diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java index 7feaeae5b..2d046550e 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java @@ -40,6 +40,7 @@ import com.jme3.renderer.ios.IosGL; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GLDebugES; import com.jme3.renderer.opengl.GLExt; +import com.jme3.renderer.opengl.GLFbo; import com.jme3.renderer.opengl.GLRenderer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -158,11 +159,11 @@ public class IGLESContext implements JmeContext { GLExt glext = (GLExt) gl; // if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugES(gl, glext); + gl = new GLDebugES(gl, glext, (GLFbo) glext); glext = (GLExt) gl; // } - renderer = new GLRenderer(gl, glext); + renderer = new GLRenderer(gl, glext, (GLFbo) glext); renderer.initialize(); input = new IosInputHandler(); diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java index 4ec649086..e47de3a50 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java @@ -33,6 +33,7 @@ package com.jme3.system.ios; 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 java.io.IOException; @@ -45,14 +46,16 @@ import java.io.InputStream; public class IosImageLoader implements AssetLoader { public Object load(AssetInfo info) throws IOException { - InputStream in = info.openStream(); + boolean flip = ((TextureKey) info.getKey()).isFlipY(); Image img = null; + InputStream in = null; try { - img = loadImageData(Image.Format.RGBA8, in); - } catch (Exception e) { - e.printStackTrace(); + in = info.openStream(); + img = loadImageData(Format.RGBA8, flip, in); } finally { - in.close(); + if (in != null) { + in.close(); + } } return img; } @@ -64,5 +67,5 @@ public class IosImageLoader implements AssetLoader { * @param inputStream the InputStream to load the image data from * @return the loaded Image */ - private static native Image loadImageData(Format format, InputStream inputStream); + private static native Image loadImageData(Format format, boolean flipY, InputStream inputStream); } diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java index 904db71cf..d29f76f62 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java @@ -36,6 +36,14 @@ import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; import com.jme3.system.JmeSystemDelegate; import com.jme3.system.NullContext; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.ios.IosAL; +import com.jme3.audio.ios.IosALC; +//import com.jme3.audio.ios.IosEFX; +import com.jme3.audio.openal.AL; +import com.jme3.audio.openal.ALAudioRenderer; +import com.jme3.audio.openal.ALC; +import com.jme3.audio.openal.EFX; import java.io.IOException; import java.io.OutputStream; import java.net.URL; @@ -89,8 +97,11 @@ public class JmeIosSystem extends JmeSystemDelegate { @Override public AudioRenderer newAudioRenderer(AppSettings settings) { - return null; - } + ALC alc = new IosALC(); + AL al = new IosAL(); + //EFX efx = new IosEFX(); + return new ALAudioRenderer(al, alc, null); + } @Override public void initialize(AppSettings settings) { diff --git a/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg new file mode 100644 index 000000000..e9d79a459 --- /dev/null +++ b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg @@ -0,0 +1,10 @@ +INCLUDE com/jme3/asset/General.cfg + +# IOS specific loaders +LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg +LOADER com.jme3.audio.plugins.OGGLoader : ogg +LOADER com.jme3.material.plugins.J3MLoader : j3m +LOADER com.jme3.material.plugins.J3MLoader : j3md +LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib +LOADER com.jme3.export.binary.BinaryImporter : j3o +LOADER com.jme3.font.plugins.BitmapFontLoader : fnt diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index dc61e6c6c..016440bf0 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -142,7 +142,6 @@ public class PhysicsSpace { 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
@@ -362,7 +361,7 @@ public class PhysicsSpace { eventFactory.recycle(physicsCollisionEvent); } } - + public static Future enqueueOnThisThread(Callable callable) { AppTask task = new AppTask(callable); System.out.println("created apptask"); @@ -620,7 +619,7 @@ public class PhysicsSpace { physicsJoints.remove(joint.getObjectId()); dynamicsWorld.removeConstraint(joint.getObjectId()); } - + public Collection getRigidBodyList(){ return new LinkedList(physicsBodies.values()); } @@ -628,19 +627,19 @@ public class PhysicsSpace { 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 @@ -658,7 +657,7 @@ public class PhysicsSpace { dynamicsWorld.getGravity(tempVec); return Converter.convert(tempVec, gravity); } - + /** * applies gravity value to all objects */ @@ -876,30 +875,22 @@ public class PhysicsSpace { 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 + * Set the number of iterations used by the contact solver. + * + * The default is 10. Use 4 for low quality, 20 for high quality. + * + * @param numIterations The number of iterations used by the contact & constraint solver. */ - public void disableDebug() { - debugManager = null; + public void setSolverNumIterations(int numIterations) { + dynamicsWorld.getSolverInfo().numIterations = numIterations; } - public AssetManager getDebugManager() { - return debugManager; + public int getSolverNumIterations() { + return dynamicsWorld.getSolverInfo().numIterations; } - + /** * interface with Broadphase types */ diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 82b8ee72b..bf99c84eb 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -13,7 +13,7 @@ import java.nio.ShortBuffer; import com.jme3.renderer.opengl.GL4; import org.lwjgl.opengl.*; -public class LwjglGL implements GL, GL2, GL3,GL4 { +public class LwjglGL implements GL, GL2, GL3, GL4 { private static void checkLimit(Buffer buffer) { if (buffer == null) { @@ -27,6 +27,9 @@ public class LwjglGL implements GL, GL2, GL3,GL4 { } } + public void resetStats() { + } + public void glActiveTexture(int param1) { GL13.glActiveTexture(param1); } @@ -237,6 +240,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 { public String glGetString(int param1) { return GL11.glGetString(param1); } + + public String glGetString(int param1, int param2) { + return GL30.glGetStringi(param1, param2); + } public boolean glIsEnabled(int param1) { return GL11.glIsEnabled(param1); @@ -444,4 +451,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 { public void glPatchParameter(int count) { GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count); } + + @Override + public void glDeleteVertexArrays(IntBuffer arrays) { + checkLimit(arrays); + ARBVertexArrayObject.glDeleteVertexArrays(arrays); + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java index 89139282a..2c6a63fd7 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java @@ -7,12 +7,8 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import org.lwjgl.opengl.ARBDrawInstanced; import org.lwjgl.opengl.ARBInstancedArrays; -import org.lwjgl.opengl.ARBPixelBufferObject; import org.lwjgl.opengl.ARBSync; import org.lwjgl.opengl.ARBTextureMultisample; -import org.lwjgl.opengl.EXTFramebufferBlit; -import org.lwjgl.opengl.EXTFramebufferMultisample; -import org.lwjgl.opengl.EXTFramebufferObject; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GLSync; @@ -30,99 +26,51 @@ public class LwjglGLExt implements GLExt { throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); } } - - public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { - EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); - } + @Override public void glBufferData(int target, IntBuffer data, int usage) { checkLimit(data); GL15.glBufferData(target, data, usage); } + @Override public void glBufferSubData(int target, long offset, IntBuffer data) { checkLimit(data); GL15.glBufferSubData(target, offset, data); } + @Override public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) { ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount); } + @Override public void glDrawBuffers(IntBuffer bufs) { checkLimit(bufs); GL20.glDrawBuffers(bufs); } + @Override public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount); } + @Override public void glGetMultisample(int pname, int index, FloatBuffer val) { checkLimit(val); ARBTextureMultisample.glGetMultisample(pname, index, val); } - public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { - EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height); - } - + @Override public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); } + @Override public void glVertexAttribDivisorARB(int index, int divisor) { ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor); } - public void glBindFramebufferEXT(int param1, int param2) { - EXTFramebufferObject.glBindFramebufferEXT(param1, param2); - } - - public void glBindRenderbufferEXT(int param1, int param2) { - EXTFramebufferObject.glBindRenderbufferEXT(param1, param2); - } - - public int glCheckFramebufferStatusEXT(int param1) { - return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1); - } - - public void glDeleteFramebuffersEXT(IntBuffer param1) { - checkLimit(param1); - EXTFramebufferObject.glDeleteFramebuffersEXT(param1); - } - - public void glDeleteRenderbuffersEXT(IntBuffer param1) { - checkLimit(param1); - EXTFramebufferObject.glDeleteRenderbuffersEXT(param1); - } - - public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { - EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4); - } - - public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { - EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5); - } - - public void glGenFramebuffersEXT(IntBuffer param1) { - checkLimit(param1); - EXTFramebufferObject.glGenFramebuffersEXT(param1); - } - - public void glGenRenderbuffersEXT(IntBuffer param1) { - checkLimit(param1); - EXTFramebufferObject.glGenRenderbuffersEXT(param1); - } - - public void glGenerateMipmapEXT(int param1) { - EXTFramebufferObject.glGenerateMipmapEXT(param1); - } - - public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { - EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4); - } - @Override public Object glFenceSync(int condition, int flags) { return ARBSync.glFenceSync(condition, flags); diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java new file mode 100644 index 000000000..159000a6c --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java @@ -0,0 +1,98 @@ +package com.jme3.renderer.lwjgl; + +import com.jme3.renderer.RendererException; +import com.jme3.renderer.opengl.GLFbo; +import java.nio.Buffer; +import java.nio.IntBuffer; +import org.lwjgl.opengl.EXTFramebufferBlit; +import org.lwjgl.opengl.EXTFramebufferMultisample; +import org.lwjgl.opengl.EXTFramebufferObject; + +/** + * Implements GLFbo via GL_EXT_framebuffer_object. + * + * @author Kirill Vainer + */ +public class LwjglGLFboEXT implements GLFbo { + + private static void checkLimit(Buffer buffer) { + if (buffer == null) { + return; + } + if (buffer.limit() == 0) { + throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); + } + if (buffer.remaining() == 0) { + throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); + } + } + + @Override + public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { + EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } + + @Override + public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { + EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height); + } + + @Override + public void glBindFramebufferEXT(int param1, int param2) { + EXTFramebufferObject.glBindFramebufferEXT(param1, param2); + } + + @Override + public void glBindRenderbufferEXT(int param1, int param2) { + EXTFramebufferObject.glBindRenderbufferEXT(param1, param2); + } + + @Override + public int glCheckFramebufferStatusEXT(int param1) { + return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1); + } + + @Override + public void glDeleteFramebuffersEXT(IntBuffer param1) { + checkLimit(param1); + EXTFramebufferObject.glDeleteFramebuffersEXT(param1); + } + + @Override + public void glDeleteRenderbuffersEXT(IntBuffer param1) { + checkLimit(param1); + EXTFramebufferObject.glDeleteRenderbuffersEXT(param1); + } + + @Override + public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { + EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4); + } + + @Override + public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { + EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5); + } + + @Override + public void glGenFramebuffersEXT(IntBuffer param1) { + checkLimit(param1); + EXTFramebufferObject.glGenFramebuffersEXT(param1); + } + + @Override + public void glGenRenderbuffersEXT(IntBuffer param1) { + checkLimit(param1); + EXTFramebufferObject.glGenRenderbuffersEXT(param1); + } + + @Override + public void glGenerateMipmapEXT(int param1) { + EXTFramebufferObject.glGenerateMipmapEXT(param1); + } + + @Override + public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { + EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4); + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java new file mode 100644 index 000000000..acc540273 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java @@ -0,0 +1,96 @@ +package com.jme3.renderer.lwjgl; + +import com.jme3.renderer.RendererException; +import com.jme3.renderer.opengl.GLFbo; +import java.nio.Buffer; +import java.nio.IntBuffer; +import org.lwjgl.opengl.GL30; + +/** + * Implements GLFbo via OpenGL3+. + * + * @author Kirill Vainer + */ +public class LwjglGLFboGL3 implements GLFbo { + + private static void checkLimit(Buffer buffer) { + if (buffer == null) { + return; + } + if (buffer.limit() == 0) { + throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); + } + if (buffer.remaining() == 0) { + throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); + } + } + + @Override + public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { + GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } + + @Override + public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { + GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height); + } + + @Override + public void glBindFramebufferEXT(int param1, int param2) { + GL30.glBindFramebuffer(param1, param2); + } + + @Override + public void glBindRenderbufferEXT(int param1, int param2) { + GL30.glBindRenderbuffer(param1, param2); + } + + @Override + public int glCheckFramebufferStatusEXT(int param1) { + return GL30.glCheckFramebufferStatus(param1); + } + + @Override + public void glDeleteFramebuffersEXT(IntBuffer param1) { + checkLimit(param1); + GL30.glDeleteFramebuffers(param1); + } + + @Override + public void glDeleteRenderbuffersEXT(IntBuffer param1) { + checkLimit(param1); + GL30.glDeleteRenderbuffers(param1); + } + + @Override + public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { + GL30.glFramebufferRenderbuffer(param1, param2, param3, param4); + } + + @Override + public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { + GL30.glFramebufferTexture2D(param1, param2, param3, param4, param5); + } + + @Override + public void glGenFramebuffersEXT(IntBuffer param1) { + checkLimit(param1); + GL30.glGenFramebuffers(param1); + } + + @Override + public void glGenRenderbuffersEXT(IntBuffer param1) { + checkLimit(param1); + GL30.glGenRenderbuffers(param1); + } + + @Override + public void glGenerateMipmapEXT(int param1) { + GL30.glGenerateMipmap(param1); + } + + @Override + public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { + GL30.glRenderbufferStorage(param1, param2, param3, param4); + } +} 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 index 1286323ef..c88f7b734 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -39,13 +39,18 @@ import com.jme3.renderer.Renderer; import com.jme3.renderer.RendererException; import com.jme3.renderer.lwjgl.LwjglGL; import com.jme3.renderer.lwjgl.LwjglGLExt; +import com.jme3.renderer.lwjgl.LwjglGLFboEXT; +import com.jme3.renderer.lwjgl.LwjglGLFboGL3; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL3; +import com.jme3.renderer.opengl.GL4; import com.jme3.renderer.opengl.GLDebugDesktop; import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLFbo; import com.jme3.renderer.opengl.GLRenderer; +import com.jme3.renderer.opengl.GLTiming; +import com.jme3.renderer.opengl.GLTimingState; import com.jme3.renderer.opengl.GLTracer; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; @@ -203,28 +208,44 @@ public abstract class LwjglContext implements JmeContext { } if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) - || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { + || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { GL gl = new LwjglGL(); - GLFbo glfbo = new LwjglGLExt(); + GLExt glext = new LwjglGLExt(); + GLFbo glfbo; + + if (GLContext.getCapabilities().OpenGL30) { + glfbo = new LwjglGLFboGL3(); + } else { + glfbo = new LwjglGLFboEXT(); + } if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugDesktop(gl, glfbo); + gl = new GLDebugDesktop(gl, glext, glfbo); + glext = (GLExt) gl; glfbo = (GLFbo) gl; } + if (settings.getBoolean("GraphicsTiming")) { + GLTimingState timingState = new GLTimingState(); + gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); + glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); + } + if (settings.getBoolean("GraphicsTrace")) { - gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLExt.class); + gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); + glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); } - renderer = new GLRenderer(gl, glfbo); + renderer = new GLRenderer(gl, glext, glfbo); renderer.initialize(); } else { throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); } if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { - ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback()); + ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler())); } renderer.setMainFrameBufferSrgb(settings.getGammaCorrection()); diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java new file mode 100644 index 000000000..c8f329f13 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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 java.util.HashMap; +import org.lwjgl.opengl.ARBDebugOutput; +import org.lwjgl.opengl.ARBDebugOutputCallback; + +class LwjglGLDebugOutputHandler implements ARBDebugOutputCallback.Handler { + + private static final HashMap constMap = new HashMap(); + private static final String MESSAGE_FORMAT = + "[JME3] OpenGL debug message\r\n" + + " ID: %d\r\n" + + " Source: %s\r\n" + + " Type: %s\r\n" + + " Severity: %s\r\n" + + " Message: %s"; + + static { + constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB, "API"); + constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_APPLICATION_ARB, "APPLICATION"); + constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_OTHER_ARB, "OTHER"); + constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, "SHADER_COMPILER"); + constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_THIRD_PARTY_ARB, "THIRD_PARTY"); + constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB, "WINDOW_SYSTEM"); + + constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB, "DEPRECATED_BEHAVIOR"); + constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_ERROR_ARB, "ERROR"); + constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB, "OTHER"); + constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PERFORMANCE_ARB, "PERFORMANCE"); + constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PORTABILITY_ARB, "PORTABILITY"); + constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB, "UNDEFINED_BEHAVIOR"); + + constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB, "HIGH"); + constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB, "MEDIUM"); + constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, "LOW"); + } + + @Override + public void handleMessage(int source, int type, int id, int severity, String message) { + String sourceStr = constMap.get(source); + String typeStr = constMap.get(type); + String severityStr = constMap.get(severity); + + System.err.println(String.format(MESSAGE_FORMAT, id, sourceStr, typeStr, severityStr, message)); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Client.java b/jme3-networking/src/main/java/com/jme3/network/Client.java index 6837a4d86..3ef9134df 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Client.java +++ b/jme3-networking/src/main/java/com/jme3/network/Client.java @@ -31,6 +31,8 @@ */ package com.jme3.network; +import com.jme3.network.service.ClientServiceManager; + /** * Represents a remote connection to a server that can be used @@ -53,6 +55,12 @@ public interface Client extends MessageConnection */ public boolean isConnected(); + /** + * Returns true if this client has been started and is still + * running. + */ + public boolean isStarted(); + /** * Returns a unique ID for this client within the remote * server or -1 if this client isn't fully connected to the @@ -72,6 +80,12 @@ public interface Client extends MessageConnection * be able to connect to. */ public int getVersion(); + + /** + * Returns the manager for client services. Client services extend + * the functionality of the client. + */ + public ClientServiceManager getServices(); /** * Sends a message to the server. diff --git a/jme3-networking/src/main/java/com/jme3/network/Server.java b/jme3-networking/src/main/java/com/jme3/network/Server.java index 72926eab7..a52cb33bf 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Server.java +++ b/jme3-networking/src/main/java/com/jme3/network/Server.java @@ -33,6 +33,8 @@ package com.jme3.network; import java.util.Collection; +import com.jme3.network.service.HostedServiceManager; + /** * Represents a host that can send and receive messages to * a set of remote client connections. @@ -54,6 +56,12 @@ public interface Server */ public int getVersion(); + /** + * Returns the manager for hosted services. Hosted services extend + * the functionality of the server. + */ + public HostedServiceManager getServices(); + /** * Sends the specified message to all connected clients. */ diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index 54e8fd7f9..b297a933b 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -37,6 +37,8 @@ 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 com.jme3.network.service.ClientServiceManager; +import com.jme3.network.service.serializer.ClientSerializerRegistrationsService; import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; @@ -54,7 +56,7 @@ import java.util.logging.Logger; */ public class DefaultClient implements Client { - static Logger log = Logger.getLogger(DefaultClient.class.getName()); + static final Logger log = Logger.getLogger(DefaultClient.class.getName()); // First two channels are reserved for reliable and // unreliable. Note: channels are endpoint specific so these @@ -80,10 +82,14 @@ public class DefaultClient implements Client private ConnectorFactory connectorFactory; + private ClientServiceManager services; + public DefaultClient( String gameName, int version ) { this.gameName = gameName; this.version = version; + this.services = new ClientServiceManager(this); + addStandardServices(); } public DefaultClient( String gameName, int version, Connector reliable, Connector fast, @@ -93,6 +99,10 @@ public class DefaultClient implements Client setPrimaryConnectors( reliable, fast, connectorFactory ); } + protected void addStandardServices() { + services.addService(new ClientSerializerRegistrationsService()); + } + protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory ) { if( reliable == null ) @@ -167,6 +177,10 @@ public class DefaultClient implements Client continue; send(ch, reg, false); } + } + + public boolean isStarted() { + return isRunning; } protected void waitForConnected() @@ -200,6 +214,11 @@ public class DefaultClient implements Client { return version; } + + public ClientServiceManager getServices() + { + return services; + } public void send( Message message ) { @@ -260,7 +279,7 @@ public class DefaultClient implements Client { checkRunning(); - closeConnections( null ); + closeConnections( null ); } protected void closeConnections( DisconnectInfo info ) @@ -268,6 +287,10 @@ public class DefaultClient implements Client if( !isRunning ) return; + // Let the services get a chance to stop before we + // kill the connection. + services.stop(); + // Send a close message // Tell the thread it's ok to die @@ -285,6 +308,9 @@ public class DefaultClient implements Client fireDisconnected(info); isRunning = false; + + // Terminate the services + services.terminate(); } public void addClientStateListener( ClientStateListener listener ) @@ -333,6 +359,12 @@ public class DefaultClient implements Client l.clientConnected( this ); } } + + protected void startServices() + { + // Let the services know we are finally started + services.start(); + } protected void fireDisconnected( DisconnectInfo info ) { @@ -391,11 +423,19 @@ public class DefaultClient implements Client // 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(); + ClientRegistrationMessage crm = (ClientRegistrationMessage)m; + // See if it has a real ID + if( crm.getId() >= 0 ) { + // Then we've gotten our real id + this.id = (int)crm.getId(); + log.log( Level.FINE, "Connection established, id:{0}.", this.id ); + connecting.countDown(); + fireConnected(); + } else { + // Else it's a message letting us know that the + // hosted services have been started + startServices(); + } return; } else if( m instanceof ChannelInfoMessage ) { // This is an interum step in the connection process and diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index 3816fbace..2b9add2ef 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -37,6 +37,8 @@ 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 com.jme3.network.service.HostedServiceManager; +import com.jme3.network.service.serializer.ServerSerializerRegistrationsService; import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; @@ -55,7 +57,7 @@ import java.util.logging.Logger; */ public class DefaultServer implements Server { - static Logger log = Logger.getLogger(DefaultServer.class.getName()); + static final Logger log = Logger.getLogger(DefaultServer.class.getName()); // First two channels are reserved for reliable and // unreliable @@ -85,6 +87,8 @@ public class DefaultServer implements Server = new MessageListenerRegistry(); private List connectionListeners = new CopyOnWriteArrayList(); + private HostedServiceManager services; + public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast ) { if( reliable == null ) @@ -92,6 +96,8 @@ public class DefaultServer implements Server this.gameName = gameName; this.version = version; + this.services = new HostedServiceManager(this); + addStandardServices(); reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true ); channels.add( reliableAdapter ); @@ -101,6 +107,10 @@ public class DefaultServer implements Server } } + protected void addStandardServices() { + services.addService(new ServerSerializerRegistrationsService()); + } + public String getGameName() { return gameName; @@ -110,6 +120,11 @@ public class DefaultServer implements Server { return version; } + + public HostedServiceManager getServices() + { + return services; + } public int addChannel( int port ) { @@ -164,7 +179,10 @@ public class DefaultServer implements Server ka.start(); } - isRunning = true; + isRunning = true; + + // Start the services + services.start(); } public boolean isRunning() @@ -177,13 +195,20 @@ public class DefaultServer implements Server if( !isRunning ) throw new IllegalStateException( "Server is not started." ); + // First stop the services since we are about to + // kill the connections they are using + services.stop(); + try { // Kill the adpaters, they will kill the kernels for( KernelAdapter ka : channels ) { ka.close(); } - isRunning = false; + isRunning = false; + + // Now terminate all of the services + services.terminate(); } catch( InterruptedException e ) { throw new RuntimeException( "Interrupted while closing", e ); } @@ -198,7 +223,7 @@ public class DefaultServer implements Server { if( connections.isEmpty() ) return; - + ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); FilterAdapter adapter = filter == null ? null : new FilterAdapter(filter); @@ -387,7 +412,14 @@ public class DefaultServer implements Server // Now we can notify the listeners about the // new connection. - fireConnectionAdded( addedConnection ); + fireConnectionAdded( addedConnection ); + + // Send a second registration message with an invalid ID + // to let the connection know that it can start its services + m = new ClientRegistrationMessage(); + m.setId(-1); + m.setReliable(true); + addedConnection.send(m); } } @@ -396,6 +428,18 @@ public class DefaultServer implements Server return endpointConnections.get(endpoint); } + protected void removeConnecting( Endpoint p ) + { + // No easy lookup for connecting Connections + // from endpoint. + for( Map.Entry e : connecting.entrySet() ) { + if( e.getValue().hasEndpoint(p) ) { + connecting.remove(e.getKey()); + return; + } + } + } + protected void connectionClosed( Endpoint p ) { if( p.isConnected() ) { @@ -411,10 +455,10 @@ public class DefaultServer implements Server // Also note: this method will be called multiple times per // HostedConnection if it has multiple endpoints. - Connection removed = null; + Connection removed; synchronized( this ) { // Just in case the endpoint was still connecting - connecting.values().remove(p); + removeConnecting(p); // And the regular management removed = (Connection)endpointConnections.remove(p); @@ -452,6 +496,16 @@ public class DefaultServer implements Server id = nextId.getAndIncrement(); channels = new Endpoint[channelCount]; } + + boolean hasEndpoint( Endpoint p ) + { + for( Endpoint e : channels ) { + if( p == e ) { + return true; + } + } + return false; + } void setChannel( int channel, Endpoint p ) { @@ -557,6 +611,7 @@ public class DefaultServer implements Server return Collections.unmodifiableSet(sessionData.keySet()); } + @Override public String toString() { return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE] diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java index a8d0d39fd..6751ecc3f 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java @@ -181,7 +181,7 @@ public class MessageProtocol Message m = (Message)obj; messages.add(m); } catch( IOException e ) { - throw new RuntimeException( "Error deserializing object", e ); + throw new RuntimeException( "Error deserializing object, clas ID:" + buffer.getShort(0), 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 index 20682ecee..bf0f7ddb1 100644 --- 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 @@ -113,7 +113,6 @@ public class UdpConnector implements Connector public ByteBuffer read() { checkClosed(); - try { DatagramPacket packet = new DatagramPacket( buffer, buffer.length ); sock.receive(packet); @@ -132,7 +131,6 @@ public class UdpConnector implements Connector public void write( ByteBuffer data ) { checkClosed(); - try { DatagramPacket p = new DatagramPacket( data.array(), data.position(), data.remaining(), remoteAddress ); diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java new file mode 100644 index 000000000..9c3896475 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java @@ -0,0 +1,190 @@ +/* + * $Id: SerializerRegistrationsMessage.java 3829 2014-11-24 07:25:43Z pspeed $ + * + * Copyright (c) 2012, Paul Speed + * All rights reserved. + */ + +package com.jme3.network.message; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.SerializerRegistration; +import com.jme3.network.serializing.serializers.FieldSerializer; +import java.util.*; +import java.util.jar.Attributes; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Holds a compiled set of message registration information that + * can be sent over the wire. The received message can then be + * used to register all of the classes using the same IDs and + * same ordering, etc.. The intent is that the server compiles + * this message once it is sure that all serializable classes have + * been registered. It can then send this to each new client and + * they can use it to register all of the classes without requiring + * exactly reproducing the same calls that the server did to register + * messages. + * + *

Normally, JME recommends that apps have a common utility method + * that they call on both client and server. However, this makes + * pluggable services nearly impossible as some central class has to + * know about all registered serializers. This message implementation + * gets around by only requiring registration on the server.

+ * + * @author Paul Speed + */ +@Serializable +public class SerializerRegistrationsMessage extends AbstractMessage { + + static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); + + public static final Set ignore = new HashSet(); + static { + // We could build this automatically but then we + // risk making a client and server out of date simply because + // their JME versions are out of date. + ignore.add(Boolean.class); + ignore.add(Float.class); + ignore.add(Boolean.class); + ignore.add(Byte.class); + ignore.add(Character.class); + ignore.add(Short.class); + ignore.add(Integer.class); + ignore.add(Long.class); + ignore.add(Float.class); + ignore.add(Double.class); + ignore.add(String.class); + + ignore.add(DisconnectMessage.class); + ignore.add(ClientRegistrationMessage.class); + + ignore.add(Date.class); + ignore.add(AbstractCollection.class); + ignore.add(AbstractList.class); + ignore.add(AbstractSet.class); + ignore.add(ArrayList.class); + ignore.add(HashSet.class); + ignore.add(LinkedHashSet.class); + ignore.add(LinkedList.class); + ignore.add(TreeSet.class); + ignore.add(Vector.class); + ignore.add(AbstractMap.class); + ignore.add(Attributes.class); + ignore.add(HashMap.class); + ignore.add(Hashtable.class); + ignore.add(IdentityHashMap.class); + ignore.add(TreeMap.class); + ignore.add(WeakHashMap.class); + ignore.add(Enum.class); + + ignore.add(GZIPCompressedMessage.class); + ignore.add(ZIPCompressedMessage.class); + + ignore.add(ChannelInfoMessage.class); + + ignore.add(SerializerRegistrationsMessage.class); + ignore.add(SerializerRegistrationsMessage.Registration.class); + } + + public static SerializerRegistrationsMessage INSTANCE; + public static Registration[] compiled; + private static final Serializer fieldSerializer = new FieldSerializer(); + + private Registration[] registrations; + + public SerializerRegistrationsMessage() { + setReliable(true); + } + + public SerializerRegistrationsMessage( Registration... registrations ) { + setReliable(true); + this.registrations = registrations; + } + + public static void compile() { + + // Let's just see what they are here + List list = new ArrayList(); + for( SerializerRegistration reg : Serializer.getSerializerRegistrations() ) { + Class type = reg.getType(); + if( ignore.contains(type) ) + continue; + if( type.isPrimitive() ) + continue; + + list.add(new Registration(reg)); + } + + if( log.isLoggable(Level.FINE) ) { + log.log( Level.FINE, "Number of registered classes:{0}", list.size()); + for( Registration reg : list ) { + log.log( Level.FINE, " {0}", reg); + } + } + compiled = list.toArray(new Registration[list.size()]); + + INSTANCE = new SerializerRegistrationsMessage(compiled); + + Serializer.setReadOnly(true); + } + + public void registerAll() { + for( Registration reg : registrations ) { + log.log( Level.INFO, "Registering:{0}", reg); + reg.register(); + } + } + + @Serializable + public static final class Registration { + + private short id; + private String className; + private String serializerClassName; + + public Registration() { + } + + public Registration( SerializerRegistration reg ) { + + this.id = reg.getId(); + this.className = reg.getType().getName(); + if( reg.getSerializer().getClass() != FieldSerializer.class ) { + this.serializerClassName = reg.getSerializer().getClass().getName(); + } + } + + public void register() { + try { + Class type = Class.forName(className); + Serializer serializer; + if( serializerClassName == null ) { + serializer = fieldSerializer; + } else { + Class serializerType = Class.forName(serializerClassName); + serializer = (Serializer)serializerType.newInstance(); + } + SerializerRegistration result = Serializer.registerClassForId(id, type, serializer); + log.log( Level.FINE, " result:{0}", result); + } catch( ClassNotFoundException e ) { + throw new RuntimeException( "Class not found attempting to register:" + this, e ); + } catch( InstantiationException e ) { + throw new RuntimeException( "Error instantiating serializer registering:" + this, e ); + } catch( IllegalAccessException e ) { + throw new RuntimeException( "Error instantiating serializer registering:" + this, e ); + } + } + + @Override + public String toString() { + return "Registration[" + id + " = " + className + ", serializer=" + serializerClassName + "]"; + } + } +} + + + diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java index a7d20da54..d4c054403 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java @@ -71,6 +71,8 @@ public abstract class Serializer { private static boolean strictRegistration = true; + private static volatile boolean locked = false; + // Registers the classes we already have serializers for. static { @@ -168,6 +170,20 @@ public abstract class Serializer { return nextAvailableId--; } + /** + * Can put the registry in a read-only state such that additional attempts + * to register classes will fail. This can be used by servers to lock the + * registry to avoid accidentally registering classes after a full registry + * set has been compiled. + */ + public static void setReadOnly( boolean b ) { + locked = b; + } + + public static boolean isReadOnly() { + return locked; + } + /** * Directly registers a class for a specific ID. Generally, use the regular * registerClass() method. This method is intended for framework code that might @@ -175,7 +191,11 @@ public abstract class Serializer { */ public static SerializerRegistration registerClassForId( short id, Class cls, Serializer serializer ) { - SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); + if( locked ) { + throw new RuntimeException("Serializer registry locked trying to register class:" + cls); + } + + SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); idRegistrations.put(id, reg); classRegistrations.put(cls, reg); diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java new file mode 100644 index 000000000..d1a848dac --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + +import com.jme3.network.Client; + +/** + * Convenient base class for ClientServices providing some default ClientService + * interface implementations as well as a few convenience methods + * such as getServiceManager() and getService(type). Subclasses + * must at least override the onInitialize() method to handle + * service initialization. + * + * @author Paul Speed + */ +public abstract class AbstractClientService extends AbstractService + implements ClientService { + + protected AbstractClientService() { + } + + /** + * Returns the client for this client service or null if + * the service is not yet attached. + */ + protected Client getClient() { + ClientServiceManager csm = getServiceManager(); + return csm == null ? null : csm.getClient(); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java new file mode 100644 index 000000000..051c0d259 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; + + +/** + * Convenient base class for HostedServices providing some default HostedService + * interface implementations as well as a few convenience methods + * such as getServiceManager() and getService(type). Subclasses + * must at least override the onInitialize() method to handle + * service initialization. + * + * @author Paul Speed + */ +public abstract class AbstractHostedService extends AbstractService + implements HostedService { + + protected AbstractHostedService() { + } + + /** + * Returns the server for this hosted service or null if + * the service is not yet attached. + */ + protected Server getServer() { + HostedServiceManager hsm = getServiceManager(); + return hsm == null ? null : hsm.getServer(); + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom new connection behavior. + */ + @Override + public void connectionAdded(Server server, HostedConnection hc) { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom leaving connection behavior. + */ + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java new file mode 100644 index 000000000..370b1c5af --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + + +/** + * Base class providing some default Service interface implementations + * as well as a few convenience methods such as getServiceManager() + * and getService(type). Subclasses must at least override the + * onInitialize() method to handle service initialization. + * + * @author Paul Speed + */ +public abstract class AbstractService implements Service { + + private S serviceManager; + + protected AbstractService() { + } + + /** + * Returns the ServiceManager that was passed to + * initialize() during service initialization. + */ + protected S getServiceManager() { + return serviceManager; + } + + /** + * Retrieves the first sibling service of the specified + * type. + */ + protected > T getService( Class type ) { + return type.cast(serviceManager.getService(type)); + } + + /** + * Initializes this service by keeping a reference to + * the service manager and calling onInitialize(). + */ + @Override + public final void initialize( S serviceManager ) { + this.serviceManager = serviceManager; + onInitialize(serviceManager); + } + + /** + * Called during initialize() for the subclass to perform + * implementation specific initialization. + */ + protected abstract void onInitialize( S serviceManager ); + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom startup behavior. + */ + @Override + public void start() { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom stop behavior. + */ + @Override + public void stop() { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom termination behavior. + */ + @Override + public void terminate( S serviceManager ) { + } + + @Override + public String toString() { + return getClass().getName() + "[serviceManager.class=" + serviceManager.getClass() + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java new file mode 100644 index 000000000..c8057cb31 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + + +/** + * Interface implemented by Client-side services that augment + * a network Client's functionality. + * + * @author Paul Speed + */ +public interface ClientService extends Service { + + /** + * Called when the service is first attached to the service + * manager. + */ + @Override + public void initialize( ClientServiceManager serviceManager ); + + /** + * Called when the service manager is started or if the + * service is added to an already started service manager. + */ + @Override + public void start(); + + /** + * Called when the service is shutting down. All services + * are stopped and any service manager resources are closed + * before the services are terminated. + */ + @Override + public void stop(); + + /** + * The service manager is fully shutting down. All services + * have been stopped and related connections closed. + */ + @Override + public void terminate( ClientServiceManager serviceManager ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java new file mode 100644 index 000000000..dc204fb2f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + +import com.jme3.network.Client; + + +/** + * Manages ClientServices on behalf of a network Client object. + * + * @author Paul Speed + */ +public class ClientServiceManager extends ServiceManager { + + private Client client; + + /** + * Creates a new ClientServiceManager for the specified network Client. + */ + public ClientServiceManager( Client client ) { + this.client = client; + } + + /** + * Returns the network Client associated with this ClientServiceManager. + */ + public Client getClient() { + return client; + } + + /** + * Returns 'this' and is what is passed to ClientService.initialize() + * and ClientService.termnate(); + */ + @Override + protected final ClientServiceManager getParent() { + return this; + } + + /** + * Adds the specified ClientService and initializes it. If the service manager + * has already been started then the service will also be started. + */ + public void addService( ClientService s ) { + super.addService(s); + } + + /** + * Adds all of the specified ClientServices and initializes them. If the service manager + * has already been started then the services will also be started. + * This is a convenience method that delegates to addService(), thus each + * service will be initialized (and possibly started) in sequence rather + * than doing them all at the end. + */ + public void addServices( ClientService... services ) { + for( ClientService s : services ) { + super.addService(s); + } + } + + /** + * Removes the specified ClientService from this service manager, stopping + * and terminating it as required. If this service manager is in a + * started state then the service will be stopped. After removal, + * the service will be terminated. + */ + public void removeService( ClientService s ) { + super.removeService(s); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java new file mode 100644 index 000000000..f522b72d6 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + +import com.jme3.network.ConnectionListener; + + +/** + * Interface implemented by Server-side services that augment + * a network Server's functionality. + * + * @author Paul Speed + */ +public interface HostedService extends Service, ConnectionListener { + + /** + * Called when the service is first attached to the service + * manager. + */ + @Override + public void initialize( HostedServiceManager serviceManager ); + + /** + * Called when the service manager is started or if the + * service is added to an already started service manager. + */ + @Override + public void start(); + + /** + * Called when the service is shutting down. All services + * are stopped and any service manager resources are closed + * before the services are terminated. + */ + @Override + public void stop(); + + /** + * The service manager is fully shutting down. All services + * have been stopped and related connections closed. + */ + @Override + public void terminate( HostedServiceManager serviceManager ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java new file mode 100644 index 000000000..3606ba44b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + +import com.jme3.network.ConnectionListener; +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; + + +/** + * Manages HostedServices on behalf of a network Server object. + * All HostedServices are automatically informed about new and + * leaving connections. + * + * @author Paul Speed + */ +public class HostedServiceManager extends ServiceManager { + + private Server server; + private ConnectionObserver connectionObserver; + + /** + * Creates a HostedServiceManager for the specified network Server. + */ + public HostedServiceManager( Server server ) { + this.server = server; + this.connectionObserver = new ConnectionObserver(); + server.addConnectionListener(connectionObserver); + } + + /** + * Returns the network Server associated with this HostedServiceManager. + */ + public Server getServer() { + return server; + } + + /** + * Returns 'this' and is what is passed to HostedService.initialize() + * and HostedService.termnate(); + */ + @Override + protected final HostedServiceManager getParent() { + return this; + } + + /** + * Adds the specified HostedService and initializes it. If the service manager + * has already been started then the service will also be started. + */ + public void addService( HostedService s ) { + super.addService(s); + } + + /** + * Adds all of the specified HostedServices and initializes them. If the service manager + * has already been started then the services will also be started. + * This is a convenience method that delegates to addService(), thus each + * service will be initialized (and possibly started) in sequence rather + * than doing them all at the end. + */ + public void addServices( HostedService... services ) { + for( HostedService s : services ) { + super.addService(s); + } + } + + /** + * Removes the specified HostedService from this service manager, stopping + * and terminating it as required. If this service manager is in a + * started state then the service will be stopped. After removal, + * the service will be terminated. + */ + public void removeService( HostedService s ) { + super.removeService(s); + } + + /** + * Called internally when a new connection has been added so that the + * services can be notified. + */ + protected void addConnection( HostedConnection hc ) { + for( Service s : getServices() ) { + ((HostedService)s).connectionAdded(server, hc); + } + } + + /** + * Called internally when a connection has been removed so that the + * services can be notified. + */ + protected void removeConnection( HostedConnection hc ) { + for( Service s : getServices() ) { + ((HostedService)s).connectionRemoved(server, hc); + } + } + + protected class ConnectionObserver implements ConnectionListener { + + @Override + public void connectionAdded(Server server, HostedConnection hc) { + addConnection(hc); + } + + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + removeConnection(hc); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/Service.java b/jme3-networking/src/main/java/com/jme3/network/service/Service.java new file mode 100644 index 000000000..f56c90dda --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/Service.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + + +/** + * The base interface for managed services. + * + * @author Paul Speed + */ +public interface Service { + + /** + * Called when the service is first attached to the service + * manager. + */ + public void initialize( S serviceManager ); + + /** + * Called when the service manager is started or if the + * service is added to an already started service manager. + */ + public void start(); + + /** + * Called when the service manager is shutting down. All services + * are stopped and any service manager resources are closed + * before the services are terminated. + */ + public void stop(); + + /** + * The service manager is fully shutting down. All services + * have been stopped and related connections closed. + */ + public void terminate( S serviceManager ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java new file mode 100644 index 000000000..b8ee7d3c4 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The base service manager class from which the HostedServiceManager + * and ClientServiceManager classes are derived. This manages the + * the underlying services and their life cycles. + * + * @author Paul Speed + */ +public abstract class ServiceManager { + + private List> services = new CopyOnWriteArrayList>(); + private volatile boolean started = false; + + protected ServiceManager() { + } + + /** + * Retreives the 'parent' of this service manager, usually + * a more specifically typed version of 'this' but it can be + * anything the seervices are expecting. + */ + protected abstract T getParent(); + + /** + * Returns the complete list of services managed by this + * service manager. This list is thread safe following the + * CopyOnWriteArrayList semantics. + */ + protected List> getServices() { + return services; + } + + /** + * Starts this service manager and all services that it contains. + * Any services added after the service manager has started will have + * their start() methods called. + */ + public void start() { + if( started ) { + return; + } + for( Service s : services ) { + s.start(); + } + started = true; + } + + /** + * Returns true if this service manager has been started. + */ + public boolean isStarted() { + return started; + } + + /** + * Stops all services and puts the service manager into a stopped state. + */ + public void stop() { + if( !started ) { + throw new IllegalStateException(getClass().getSimpleName() + " not started."); + } + for( Service s : services ) { + s.stop(); + } + started = false; + } + + /** + * Adds the specified service and initializes it. If the service manager + * has already been started then the service will also be started. + */ + public > void addService( S s ) { + services.add(s); + s.initialize(getParent()); + if( started ) { + s.start(); + } + } + + /** + * Removes the specified service from this service manager, stopping + * and terminating it as required. If this service manager is in a + * started state then the service will be stopped. After removal, + * the service will be terminated. + */ + public > void removeService( S s ) { + if( started ) { + s.stop(); + } + services.remove(s); + s.terminate(getParent()); + } + + /** + * Terminates all services. If the service manager has not been + * stopped yet then it will be stopped. + */ + public void terminate() { + if( started ) { + stop(); + } + for( Service s : services ) { + s.terminate(getParent()); + } + } + + /** + * Retrieves the first service of the specified type. + */ + public > S getService( Class type ) { + for( Service s : services ) { + if( type.isInstance(s) ) { + return type.cast(s); + } + } + return null; + } + + @Override + public String toString() { + return getClass().getName() + "[services=" + services + "]"; + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java new file mode 100644 index 000000000..a74a7c7d1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.rpc; + +import com.jme3.network.Client; +import com.jme3.network.util.ObjectMessageDelegator; +import com.jme3.network.service.AbstractClientService; +import com.jme3.network.service.ClientServiceManager; + + +/** + * RPC service that can be added to a network Client to + * add RPC send/receive capabilities. Remote procedure + * calls can be made to the server and responses retrieved. + * Any remote procedure calls that the server performs for + * this connection will be received by this service and delegated + * to the appropriate RpcHandlers. + * + * @author Paul Speed + */ +public class RpcClientService extends AbstractClientService { + + private RpcConnection rpc; + private ObjectMessageDelegator delegator; + + /** + * Creates a new RpcClientService that can be registered + * with the network Client object. + */ + public RpcClientService() { + } + + /** + * Returns the underlying RPC connection for use by other + * services that may require a more generic non-client/server + * specific RPC object with which to interact. + */ + public RpcConnection getRpcConnection() { + return rpc; + } + + /** + * Used internally to setup the RpcConnection and MessageDelegator. + */ + @Override + protected void onInitialize( ClientServiceManager serviceManager ) { + Client client = serviceManager.getClient(); + this.rpc = new RpcConnection(client); + + delegator = new ObjectMessageDelegator(rpc, true); + client.addMessageListener(delegator, delegator.getMessageTypes()); + } + + /** + * Used internally to unregister the RPC MessageDelegator that + * was previously added to the network Client. + */ + @Override + public void terminate( ClientServiceManager serviceManager ) { + Client client = serviceManager.getClient(); + client.removeMessageListener(delegator, delegator.getMessageTypes()); + } + + /** + * Performs a synchronous call on the server against the specified + * object using the specified procedure ID. Both inboud and outbound + * communication is done on the specified channel. + */ + public Object callAndWait( byte channel, short objId, short procId, Object... args ) { + return rpc.callAndWait(channel, objId, procId, args); + } + + /** + * Performs an asynchronous call on the server against the specified + * object using the specified procedure ID. Communication is done + * over the specified channel. No responses are received and none + * are waited for. + */ + public void callAsync( byte channel, short objId, short procId, Object... args ) { + rpc.callAsync(channel, objId, procId, args); + } + + /** + * Register a handler that will be called when the server + * performs a remove procedure call against this client. + * Only one handler per object ID can be registered at any given time, + * though the same handler can be registered for multiple object + * IDs. + */ + public void registerHandler( short objId, RpcHandler handler ) { + rpc.registerHandler(objId, handler); + } + + /** + * Removes a previously registered handler for the specified + * object ID. + */ + public void removeHandler( short objId, RpcHandler handler ) { + rpc.removeHandler(objId, handler); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java new file mode 100644 index 000000000..f457e426b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.rpc; + +import com.jme3.network.MessageConnection; +import com.jme3.network.service.rpc.msg.RpcCallMessage; +import com.jme3.network.service.rpc.msg.RpcResponseMessage; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Wraps a message connection to provide RPC call support. This + * is used internally by the RpcClientService and RpcHostedService to manage + * network messaging. + * + * @author Paul Speed + */ +public class RpcConnection { + + static final Logger log = Logger.getLogger(RpcConnection.class.getName()); + + /** + * The underlying connection upon which RPC call messages are sent + * and RPC response messages are received. It can be a Client or + * a HostedConnection depending on the mode of the RPC service. + */ + private MessageConnection connection; + + /** + * The objectId index of RpcHandler objects that are used to perform the + * RPC calls for a particular object. + */ + private Map handlers = new ConcurrentHashMap(); + + /** + * Provides unique messages IDs for outbound synchronous call + * messages. These are then used in the responses index to + * locate the proper ResponseHolder objects. + */ + private AtomicLong sequenceNumber = new AtomicLong(); + + /** + * Tracks the ResponseHolder objects for sent message IDs. When the + * response is received, the appropriate handler is found here and the + * response or error set, thus releasing the waiting caller. + */ + private Map responses = new ConcurrentHashMap(); + + /** + * Creates a new RpcConnection for the specified network connection. + */ + public RpcConnection( MessageConnection connection ) { + this.connection = connection; + } + + /** + * Clears any pending synchronous calls causing them to + * throw an exception with the message "Closing connection". + */ + public void close() { + // Let any pending waits go free + for( ResponseHolder holder : responses.values() ) { + holder.release(); + } + } + + /** + * Performs a remote procedure call with the specified arguments and waits + * for the response. Both the outbound message and inbound response will + * be sent on the specified channel. + */ + public Object callAndWait( byte channel, short objId, short procId, Object... args ) { + + RpcCallMessage msg = new RpcCallMessage(sequenceNumber.getAndIncrement(), + channel, objId, procId, args); + + // Need to register an object so we can wait for the response. + // ...before we send it. Just in case. + ResponseHolder holder = new ResponseHolder(msg); + responses.put(msg.getMessageId(), holder); + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); + } + + // Prevent non-async messages from being send as UDP + // because there is a high probabilty that this would block + // forever waiting for a response. For async calls it's ok + // so it doesn't do the check. + if( channel >= 0 ) { + connection.send(channel, msg); + } else { + connection.send(msg); + } + + return holder.getResponse(); + } + + /** + * Performs a remote procedure call with the specified arguments but does + * not wait for a response. The outbound message is sent on the specified channel. + * There is no inbound response message. + */ + public void callAsync( byte channel, short objId, short procId, Object... args ) { + + RpcCallMessage msg = new RpcCallMessage(-1, channel, objId, procId, args); + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); + } + connection.send(channel, msg); + } + + /** + * Register a handler that can be called by the other end + * of the connection using the specified object ID. Only one + * handler per object ID can be registered at any given time, + * though the same handler can be registered for multiple object + * IDs. + */ + public void registerHandler( short objId, RpcHandler handler ) { + handlers.put(objId, handler); + } + + /** + * Removes a previously registered handler for the specified + * object ID. + */ + public void removeHandler( short objId, RpcHandler handler ) { + RpcHandler removing = handlers.get(objId); + if( handler != removing ) { + throw new IllegalArgumentException("Handler not registered for object ID:" + + objId + ", handler:" + handler ); + } + handlers.remove(objId); + } + + protected void send( byte channel, RpcResponseMessage msg ) { + if( channel >= 0 ) { + connection.send(channel, msg); + } else { + connection.send(msg); + } + } + + /** + * Called internally when an RpcCallMessage is received from + * the remote connection. + */ + public void handleMessage( RpcCallMessage msg ) { + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "handleMessage({0})", msg); + } + RpcHandler handler = handlers.get(msg.getObjectId()); + try { + if( handler == null ) { + throw new RuntimeException("Handler not found for objectID:" + msg.getObjectId()); + } + Object result = handler.call(this, msg.getObjectId(), msg.getProcedureId(), msg.getArguments()); + if( !msg.isAsync() ) { + send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), result)); + } + } catch( Exception e ) { + if( !msg.isAsync() ) { + send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), e)); + } else { + log.log(Level.SEVERE, "Error invoking async call for:" + msg, e); + } + } + } + + /** + * Called internally when an RpcResponseMessage is received from + * the remote connection. + */ + public void handleMessage( RpcResponseMessage msg ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "handleMessage({0})", msg); + } + ResponseHolder holder = responses.remove(msg.getMessageId()); + if( holder == null ) { + return; + } + holder.setResponse(msg); + } + + /** + * Sort of like a Future, holds a locked reference to a response + * until the remote call has completed and returned a response. + */ + private class ResponseHolder { + private Object response; + private String error; + private RpcCallMessage msg; + boolean received = false; + + public ResponseHolder( RpcCallMessage msg ) { + this.msg = msg; + } + + public synchronized void setResponse( RpcResponseMessage msg ) { + this.response = msg.getResult(); + this.error = msg.getError(); + this.received = true; + notifyAll(); + } + + public synchronized Object getResponse() { + try { + while(!received) { + wait(); + } + } catch( InterruptedException e ) { + throw new RuntimeException("Interrupted waiting for respone to:" + msg, e); + } + if( error != null ) { + throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error); + } + return response; + } + + public synchronized void release() { + if( received ) { + return; + } + // Else signal an error for the callers + this.error = "Closing connection"; + this.received = true; + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java new file mode 100644 index 000000000..d7d99981d --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.rpc; + + +/** + * Implementations of this interface can be registered with + * the RpcClientService or RpcHostService to handle the + * remote procedure calls for a given object or objects. + * + * @author Paul Speed + */ +public interface RpcHandler { + + /** + * Called when a remote procedure call request is received for a particular + * object from the other end of the network connection. + */ + public Object call( RpcConnection conn, short objectId, short procId, Object... args ); +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java new file mode 100644 index 000000000..4a347b5a3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.rpc; + +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.util.SessionDataDelegator; +import com.jme3.network.service.AbstractHostedService; +import com.jme3.network.service.HostedServiceManager; +import com.jme3.network.service.rpc.msg.RpcCallMessage; +import com.jme3.network.service.rpc.msg.RpcResponseMessage; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * RPC service that can be added to a network Server to + * add RPC send/receive capabilities. For a particular + * HostedConnection, Remote procedure calls can be made to the + * associated Client and responses retrieved. Any remote procedure + * calls that the Client performs for this connection will be + * received by this service and delegated to the appropriate RpcHandlers. + * + * Note: it can be dangerous for a server to perform synchronous + * RPC calls to a client but especially so if not done as part + * of the response to some other message. ie: iterating over all + * or some HostedConnections to perform synchronous RPC calls + * will be slow and potentially block the server's threads in ways + * that can cause deadlocks or odd contention. + * + * @author Paul Speed + */ +public class RpcHostedService extends AbstractHostedService { + + private static final String ATTRIBUTE_NAME = "rpcSession"; + + static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); + + private boolean autoHost; + private SessionDataDelegator delegator; + + /** + * Creates a new RPC host service that can be registered + * with the Network server and will automatically 'host' + * RPC services and each new network connection. + */ + public RpcHostedService() { + this(true); + } + + /** + * Creates a new RPC host service that can be registered + * with the Network server and will optionally 'host' + * RPC services and each new network connection depending + * on the specified 'autoHost' flag. + */ + public RpcHostedService( boolean autoHost ) { + this.autoHost = autoHost; + + // This works for me... has to be different in + // the general case + Serializer.registerClasses(RpcCallMessage.class, RpcResponseMessage.class); + } + + /** + * Used internally to setup the message delegator that will + * handle HostedConnection specific messages and forward them + * to that connection's RpcConnection. + */ + @Override + protected void onInitialize( HostedServiceManager serviceManager ) { + Server server = serviceManager.getServer(); + + // A general listener for forwarding the messages + // to the client-specific handler + this.delegator = new SessionDataDelegator(RpcConnection.class, + ATTRIBUTE_NAME, + true); + server.addMessageListener(delegator, delegator.getMessageTypes()); + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Registered delegator for message types:{0}", Arrays.asList(delegator.getMessageTypes())); + } + } + + /** + * When set to true, all new connections will automatically have + * RPC hosting services attached to them, meaning they can send + * and receive RPC calls. If this is set to false then it is up + * to other services to eventually call startHostingOnConnection(). + * + *

Reasons for doing this vary but usually would be because + * the client shouldn't be allowed to perform any RPC calls until + * it has provided more information. In general, this is unnecessary + * because the RpcHandler registries are not shared. Each client + * gets their own and RPC calls will fail until the appropriate + * objects have been registtered.

+ */ + public void setAutoHost( boolean b ) { + this.autoHost = b; + } + + /** + * Returns true if this service automatically attaches RPC + * hosting capabilities to new connections. + */ + public boolean getAutoHost() { + return autoHost; + } + + /** + * Retrieves the RpcConnection for the specified HostedConnection + * if that HostedConnection has had RPC services started using + * startHostingOnConnection() (or via autohosting). Returns null + * if the connection currently doesn't have RPC hosting services + * attached. + */ + public RpcConnection getRpcConnection( HostedConnection hc ) { + return hc.getAttribute(ATTRIBUTE_NAME); + } + + /** + * Sets up RPC hosting services for the hosted connection allowing + * getRpcConnection() to return a valid RPC connection object. + * This method is called automatically for all new connections if + * autohost is set to true. + */ + public void startHostingOnConnection( HostedConnection hc ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "startHostingOnConnection:{0}", hc); + } + hc.setAttribute(ATTRIBUTE_NAME, new RpcConnection(hc)); + } + + /** + * Removes any RPC hosting services associated with the specified + * connection. Calls to getRpcConnection() will return null for + * this connection. The connection's RpcConnection is also closed, + * releasing any waiting synchronous calls with a "Connection closing" + * error. + * This method is called automatically for all leaving connections if + * autohost is set to true. + */ + public void stopHostingOnConnection( HostedConnection hc ) { + RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME); + if( rpc == null ) { + return; + } + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc); + } + hc.setAttribute(ATTRIBUTE_NAME, null); + rpc.close(); + } + + /** + * Used internally to remove the message delegator from the + * server. + */ + @Override + public void terminate(HostedServiceManager serviceManager) { + Server server = serviceManager.getServer(); + server.removeMessageListener(delegator, delegator.getMessageTypes()); + } + + /** + * Called internally when a new connection is detected for + * the server. If the current autoHost property is true then + * startHostingOnConnection(hc) is called. + */ + @Override + public void connectionAdded(Server server, HostedConnection hc) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc}); + } + if( autoHost ) { + startHostingOnConnection(hc); + } + } + + /** + * Called internally when an existing connection is leaving + * the server. If the current autoHost property is true then + * stopHostingOnConnection(hc) is called. + */ + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc}); + } + stopHostingOnConnection(hc); + } + +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java new file mode 100644 index 000000000..70f12f1e0 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.rpc.msg; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; + + +/** + * Used internally to send RPC call information to + * the other end of a connection for execution. + * + * @author Paul Speed + */ +@Serializable +public class RpcCallMessage extends AbstractMessage { + + private long msgId; + private byte channel; + private short objId; + private short procId; + private Object[] args; + + public RpcCallMessage() { + } + + public RpcCallMessage( long msgId, byte channel, short objId, short procId, Object... args ) { + this.msgId = msgId; + this.channel = channel; + this.objId = objId; + this.procId = procId; + this.args = args; + } + + public long getMessageId() { + return msgId; + } + + public byte getChannel() { + return channel; + } + + public boolean isAsync() { + return msgId == -1; + } + + public short getObjectId() { + return objId; + } + + public short getProcedureId() { + return procId; + } + + public Object[] getArguments() { + return args; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[#" + msgId + ", channel=" + channel + + (isAsync() ? ", async" : ", sync") + + ", objId=" + objId + + ", procId=" + procId + + ", args.length=" + args.length + + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java new file mode 100644 index 000000000..efb0def6a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.rpc.msg; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; +import java.io.PrintWriter; +import java.io.StringWriter; + + +/** + * Used internally to send an RPC call's response back to + * the caller. + * + * @author Paul Speed + */ +@Serializable +public class RpcResponseMessage extends AbstractMessage { + + private long msgId; + private Object result; + private String error; + + public RpcResponseMessage() { + } + + public RpcResponseMessage( long msgId, Object result ) { + this.msgId = msgId; + this.result = result; + } + + public RpcResponseMessage( long msgId, Throwable t ) { + this.msgId = msgId; + + StringWriter sOut = new StringWriter(); + PrintWriter out = new PrintWriter(sOut); + t.printStackTrace(out); + out.close(); + this.error = sOut.toString(); + } + + public long getMessageId() { + return msgId; + } + + public Object getResult() { + return result; + } + + public String getError() { + return error; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[#" + msgId + ", result=" + result + + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java new file mode 100644 index 000000000..911ce0fb1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.serializer; + +import com.jme3.network.Client; +import com.jme3.network.Message; +import com.jme3.network.MessageListener; +import com.jme3.network.message.SerializerRegistrationsMessage; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.service.AbstractClientService; +import com.jme3.network.service.ClientServiceManager; + + +/** + * + * + * @author Paul Speed + */ +public class ClientSerializerRegistrationsService extends AbstractClientService + implements MessageListener { + + @Override + protected void onInitialize( ClientServiceManager serviceManager ) { + // Make sure our message type is registered + // This is the minimum we'd need just to be able to register + // the rest... otherwise we can't even receive this message. + Serializer.registerClass(SerializerRegistrationsMessage.class); + Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); + + // Add our listener for that message type + serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class); + } + + public void messageReceived( Client source, Message m ) { + // We only wait for one kind of message... + SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m; + msg.registerAll(); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java new file mode 100644 index 000000000..b24a8db5f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.network.service.serializer; + +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; +import com.jme3.network.message.SerializerRegistrationsMessage; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.service.AbstractHostedService; +import com.jme3.network.service.HostedServiceManager; + + +/** + * + * + * @author Paul Speed + */ +public class ServerSerializerRegistrationsService extends AbstractHostedService { + + @Override + protected void onInitialize( HostedServiceManager serviceManager ) { + // Make sure our message type is registered + Serializer.registerClass(SerializerRegistrationsMessage.class); + Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); + } + + @Override + public void start() { + // Compile the registrations into a message we will + // send to all connecting clients + SerializerRegistrationsMessage.compile(); + } + + @Override + public void connectionAdded(Server server, HostedConnection hc) { + // Just in case + super.connectionAdded(server, hc); + + // Send the client the registration information + hc.send(SerializerRegistrationsMessage.INSTANCE); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java new file mode 100644 index 000000000..a73496ea8 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.util; + +import com.jme3.network.Message; +import com.jme3.network.MessageConnection; +import com.jme3.network.MessageListener; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +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; + + +/** + * A MessageListener implementation that will forward messages to methods + * of a delegate object. These methods can be automapped or manually + * specified. Subclasses provide specific implementations for how to + * find the actual delegate object. + * + * @author Paul Speed + */ +public abstract class AbstractMessageDelegator + implements MessageListener { + + static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName()); + + private Class delegateType; + private Map methods = new HashMap(); + private Class[] messageTypes; + + /** + * Creates an AbstractMessageDelegator that will forward received + * messages to methods of the specified delegate type. If automap + * is true then reflection is used to lookup probably message handling + * methods. + */ + protected AbstractMessageDelegator( Class delegateType, boolean automap ) { + this.delegateType = delegateType; + if( automap ) { + automap(); + } + } + + /** + * Returns the array of messages known to be handled by this message + * delegator. + */ + public Class[] getMessageTypes() { + if( messageTypes == null ) { + messageTypes = methods.keySet().toArray(new Class[methods.size()]); + } + return messageTypes; + } + + /** + * Returns true if the specified method is valid for the specified + * message type. This is used internally during automapping to + * provide implementation specific filting of methods. + * This implementation checks for methods that take either the connection and message + * type arguments (in that order) or just the message type. + */ + protected boolean isValidMethod( Method m, Class messageType ) { + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "isValidMethod({0}, {1})", new Object[]{m, messageType}); + } + + // Parameters must be S and message type or just message type + Class[] parms = m.getParameterTypes(); + if( parms.length != 2 && parms.length != 1 ) { + log.finest("Parameter count is not 1 or 2"); + return false; + } + int messageIndex = 0; + if( parms.length > 1 ) { + if( MessageConnection.class.isAssignableFrom(parms[0]) ) { + messageIndex++; + } else { + log.finest("First paramter is not a MessageConnection or subclass."); + return false; + } + } + + if( messageType == null && !Message.class.isAssignableFrom(parms[messageIndex]) ) { + log.finest("Second paramter is not a Message or subclass."); + return false; + } + if( messageType != null && !parms[messageIndex].isAssignableFrom(messageType) ) { + log.log(Level.FINEST, "Second paramter is not a {0}", messageType); + return false; + } + return true; + } + + /** + * Convenience method that returns the message type as + * reflecively determined for a particular method. This + * only works with methods that actually have arguments. + * This implementation returns the last element of the method's + * getParameterTypes() array, thus supporting both + * method(connection, messageType) as well as just method(messageType) + * calling forms. + */ + protected Class getMessageType( Method m ) { + Class[] parms = m.getParameterTypes(); + return parms[parms.length-1]; + } + + /** + * Goes through all of the delegate type's methods to find + * a method of the specified name that may take the specified + * message type. + */ + protected Method findDelegate( String name, Class messageType ) { + // We do an exhaustive search because it's easier to + // check for a variety of parameter types and it's all + // that Class would be doing in getMethod() anyway. + for( Method m : delegateType.getDeclaredMethods() ) { + + if( !m.getName().equals(name) ) { + continue; + } + + if( isValidMethod(m, messageType) ) { + return m; + } + } + + return null; + } + + /** + * Returns true if the specified method name is allowed. + * This is used by automapping to determine if a method + * should be rejected purely on name. Default implemention + * always returns true. + */ + protected boolean allowName( String name ) { + return true; + } + + /** + * Calls the map(Set) method with a null argument causing + * all available matching methods to mapped to message types. + */ + protected final void automap() { + map((Set)null); + if( methods.isEmpty() ) { + throw new RuntimeException("No message handling methods found for class:" + delegateType); + } + } + + /** + * Specifically maps the specified methods names, autowiring + * the parameters. + */ + public AbstractMessageDelegator map( String... methodNames ) { + Set names = new HashSet( Arrays.asList(methodNames) ); + map(names); + return this; + } + + /** + * Goes through all of the delegate type's declared methods + * mapping methods that match the current constraints. + * If the constraints set is null then allowName() is + * checked for names otherwise only names in the constraints + * set are allowed. + * For each candidate method that passes the above checks, + * isValidMethod() is called with a null message type argument. + * All methods are made accessible thus supporting non-public + * methods as well as public methods. + */ + protected void map( Set constraints ) { + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "map({0})", constraints); + } + for( Method m : delegateType.getDeclaredMethods() ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Checking method:{0}", m); + } + + if( constraints == null && !allowName(m.getName()) ) { + log.finest("Name is not allowed."); + continue; + } + if( constraints != null && !constraints.contains(m.getName()) ) { + log.finest("Name is not in constraints set."); + continue; + } + + if( isValidMethod(m, null) ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{getMessageType(m), m}); + } + // Make sure we can access the method even if it's not public or + // is in a non-public inner class. + m.setAccessible(true); + methods.put(getMessageType(m), m); + } + } + + messageTypes = null; + } + + /** + * Manually maps a specified method to the specified message type. + */ + public AbstractMessageDelegator map( Class messageType, String methodName ) { + // Lookup the method + Method m = findDelegate( methodName, messageType ); + if( m == null ) { + throw new RuntimeException( "Method:" + methodName + + " not found matching signature (MessageConnection, " + + messageType.getName() + ")" ); + } + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{messageType, m}); + } + methods.put( messageType, m ); + messageTypes = null; + return this; + } + + /** + * Returns the mapped method for the specified message type. + */ + protected Method getMethod( Class c ) { + Method m = methods.get(c); + return m; + } + + /** + * Implemented by subclasses to provide the actual delegate object + * against which the mapped message type methods will be called. + */ + protected abstract Object getSourceDelegate( S source ); + + /** + * Implementation of the MessageListener's messageReceived() + * method that will use the current message type mapping to + * find an appropriate message handling method and call it + * on the delegate returned by getSourceDelegate(). + */ + @Override + public void messageReceived( S source, Message msg ) { + if( msg == null ) { + return; + } + + Object delegate = getSourceDelegate(source); + if( delegate == null ) { + // Means ignore this message/source + return; + } + + Method m = getMethod(msg.getClass()); + if( m == null ) { + throw new RuntimeException("Delegate method not found for message class:" + + msg.getClass()); + } + + try { + if( m.getParameterTypes().length > 1 ) { + m.invoke( delegate, source, msg ); + } else { + m.invoke( delegate, msg ); + } + } catch( IllegalAccessException e ) { + throw new RuntimeException("Error executing:" + m, e); + } catch( InvocationTargetException e ) { + throw new RuntimeException("Error executing:" + m, e.getCause()); + } + } +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java new file mode 100644 index 000000000..b92ee09a8 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.util; + +import com.jme3.network.MessageConnection; + + +/** + * A MessageListener implementation that will forward messages to methods + * of a specified delegate object. These methods can be automapped or manually + * specified. + * + * @author Paul Speed + */ +public class ObjectMessageDelegator extends AbstractMessageDelegator { + + private Object delegate; + + /** + * Creates a MessageListener that will forward mapped message types + * to methods of the specified object. + * If automap is true then all methods with the proper signature will + * be mapped. + *

Methods of the following signatures are allowed: + *

    + *
  • void someName(S conn, SomeMessage msg) + *
  • void someName(Message msg) + *
+ * Where S is the type of MessageConnection and SomeMessage is some + * specific concreate Message subclass. + */ + public ObjectMessageDelegator( Object delegate, boolean automap ) { + super(delegate.getClass(), automap); + this.delegate = delegate; + } + + @Override + protected Object getSourceDelegate( MessageConnection source ) { + return delegate; + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java new file mode 100644 index 000000000..f2bee3373 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.util; + +import com.jme3.network.HostedConnection; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A MessageListener implementation that will forward messages to methods + * of a delegate specified as a HostedConnection session attribute. This is + * useful for handling connection-specific messages from clients that must + * delegate to client-specific data objects. + * The delegate methods can be automapped or manually specified. + * + * @author Paul Speed + */ +public class SessionDataDelegator extends AbstractMessageDelegator { + + static final Logger log = Logger.getLogger(SessionDataDelegator.class.getName()); + + private String attributeName; + + /** + * Creates a MessageListener that will forward mapped message types + * to methods of an object specified as a HostedConnection attribute. + * If automap is true then all methods with the proper signature will + * be mapped. + *

Methods of the following signatures are allowed: + *

    + *
  • void someName(S conn, SomeMessage msg) + *
  • void someName(Message msg) + *
+ * Where S is the type of MessageConnection and SomeMessage is some + * specific concreate Message subclass. + */ + public SessionDataDelegator( Class delegateType, String attributeName, boolean automap ) { + super(delegateType, automap); + this.attributeName = attributeName; + } + + /** + * Returns the attribute name that will be used to look up the + * delegate object. + */ + public String getAttributeName() { + return attributeName; + } + + /** + * Called internally when there is no session object + * for the current attribute name attached to the passed source + * HostConnection. Default implementation logs a warning. + */ + protected void miss( HostedConnection source ) { + log.log(Level.WARNING, "Session data is null for:{0} on connection:{1}", new Object[]{attributeName, source}); + } + + /** + * Returns the attributeName attribute of the supplied source + * HostConnection. If there is no value at that attribute then + * the miss() method is called. + */ + protected Object getSourceDelegate( HostedConnection source ) { + Object result = source.getAttribute(attributeName); + if( result == null ) { + miss(source); + } + return result; + } +} + diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java index 06a4ec795..35375f9c5 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java @@ -32,6 +32,8 @@ package com.jme3.scene.plugins.fbx; import com.jme3.asset.TextureKey; +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; @@ -56,6 +58,13 @@ public class ContentTextureKey extends TextureKey { return content; } + @Override + public Class getCacheType(){ + // Need to override this so that textures embedded in FBX + // don't get cached by the asset manager. + return null; + } + @Override public String toString() { return super.toString() + " " + content.length + " bytes"; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java new file mode 100644 index 000000000..fd157cb48 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx; + +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.Track; +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.ModelKey; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.fbx.anim.FbxToJmeTrack; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; +import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer; +import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; +import com.jme3.scene.plugins.fbx.anim.FbxBindPose; +import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; +import com.jme3.scene.plugins.fbx.file.FbxDump; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.file.FbxFile; +import com.jme3.scene.plugins.fbx.file.FbxReader; +import com.jme3.scene.plugins.fbx.file.FbxId; +import com.jme3.scene.plugins.fbx.misc.FbxGlobalSettings; +import com.jme3.scene.plugins.fbx.node.FbxNode; +import com.jme3.scene.plugins.fbx.node.FbxRootNode; +import com.jme3.scene.plugins.fbx.obj.FbxObjectFactory; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(FbxLoader.class.getName()); + + private AssetManager assetManager; + + private String sceneName; + private String sceneFilename; + private String sceneFolderName; + private FbxGlobalSettings globalSettings; + private final Map objectMap = new HashMap(); + + private final List animStacks = new ArrayList(); + private final List bindPoses = new ArrayList(); + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + this.assetManager = assetInfo.getManager(); + AssetKey assetKey = assetInfo.getKey(); + if (!(assetKey instanceof ModelKey)) { + throw new AssetLoadException("Invalid asset key"); + } + + InputStream stream = assetInfo.openStream(); + try { + sceneFilename = assetKey.getName(); + sceneFolderName = assetKey.getFolder(); + String ext = assetKey.getExtension(); + + sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1); + if (sceneFolderName != null && sceneFolderName.length() > 0) { + sceneName = sceneName.substring(sceneFolderName.length()); + } + + reset(); + + // Load the data from the stream. + loadData(stream); + + // Bind poses are needed to compute world transforms. + applyBindPoses(); + + // Need world transforms for skeleton creation. + updateWorldTransforms(); + + // Need skeletons for meshs to be created in scene graph construction. + // Mesh bone indices require skeletons to determine bone index. + constructSkeletons(); + + // Create the jME3 scene graph from the FBX scene graph. + // Also creates SkeletonControls based on the constructed skeletons. + Spatial scene = constructSceneGraph(); + + // Load animations into AnimControls + constructAnimations(); + + return scene; + } finally { + releaseObjects(); + if (stream != null) { + stream.close(); + } + } + } + + private void reset() { + globalSettings = new FbxGlobalSettings(); + } + + private void releaseObjects() { + globalSettings = null; + objectMap.clear(); + animStacks.clear(); + } + + private void loadData(InputStream stream) throws IOException { + FbxFile scene = FbxReader.readFBX(stream); + + FbxDump.dumpFile(scene); + + // TODO: Load FBX object templates + + for (FbxElement e : scene.rootElements) { + if (e.id.equals("FBXHeaderExtension")) { + loadHeader(e); + } else if (e.id.equals("GlobalSettings")) { + loadGlobalSettings(e); + } else if (e.id.equals("Objects")) { + loadObjects(e); + } else if (e.id.equals("Connections")) { + connectObjects(e); + } + } + } + + private void loadHeader(FbxElement element) { + for (FbxElement e : element.children) { + if (e.id.equals("FBXVersion")) { + Integer version = (Integer) e.properties.get(0); + if (version < 7100) { + logger.log(Level.WARNING, "FBX file version is older than 7.1. " + + "Some features may not work."); + } + } + } + } + + private void loadGlobalSettings(FbxElement element) { + globalSettings = new FbxGlobalSettings(); + globalSettings.fromElement(element); + } + + private void loadObjects(FbxElement element) { + // Initialize the FBX root element. + objectMap.put(FbxId.ROOT, new FbxRootNode(assetManager, sceneFolderName)); + + for(FbxElement e : element.children) { + if (e.id.equals("GlobalSettings")) { + // Old FBX files seem to have the GlobalSettings element + // under Objects (??) + globalSettings.fromElement(e); + } else { + FbxObject object = FbxObjectFactory.createObject(e, assetManager, sceneFolderName); + if (object != null) { + if (objectMap.containsKey(object.getId())) { + logger.log(Level.WARNING, "An object with ID \"{0}\" has " + + "already been defined. " + + "Ignoring.", + object.getId()); + } + + objectMap.put(object.getId(), object); + + if (object instanceof FbxAnimStack) { + // NOTE: animation stacks are implicitly global. + // Capture them here. + animStacks.add((FbxAnimStack) object); + } else if (object instanceof FbxBindPose) { + bindPoses.add((FbxBindPose) object); + } + } else { + throw new UnsupportedOperationException("Failed to create FBX object of type: " + e.id); + } + } + } + } + + private void removeUnconnectedObjects() { + for (FbxObject object : new ArrayList(objectMap.values())) { + if (!object.isJmeObjectCreated()) { + logger.log(Level.WARNING, "Purging orphan FBX object: {0}", object); + objectMap.remove(object.getId()); + } + } + } + + private void connectObjects(FbxElement element) { + if (objectMap.isEmpty()) { + logger.log(Level.WARNING, "FBX file is missing object information"); + return; + } else if (objectMap.size() == 1) { + // Only root node (automatically added by jME3) + logger.log(Level.WARNING, "FBX file has no objects"); + return; + } + + for (FbxElement el : element.children) { + if (!el.id.equals("C") && !el.id.equals("Connect")) { + continue; + } + String type = (String) el.properties.get(0); + FbxId childId; + FbxId parentId; + if (type.equals("OO")) { + childId = FbxId.create(el.properties.get(1)); + parentId = FbxId.create(el.properties.get(2)); + FbxObject child = objectMap.get(childId); + FbxObject parent; + + if (parentId.isNull()) { + // TODO: maybe clean this up a bit.. + parent = objectMap.get(FbxId.ROOT); + } else { + parent = objectMap.get(parentId); + } + + if (parent == null) { + throw new UnsupportedOperationException("Cannot find parent object ID \"" + parentId + "\""); + } + + parent.connectObject(child); + } else if (type.equals("OP")) { + childId = FbxId.create(el.properties.get(1)); + parentId = FbxId.create(el.properties.get(2)); + String propName = (String) el.properties.get(3); + FbxObject child = objectMap.get(childId); + FbxObject parent = objectMap.get(parentId); + parent.connectObjectProperty(child, propName); + } else { + logger.log(Level.WARNING, "Unknown connection type: {0}. Ignoring.", type); + } + } + } + + /** + * Copies the bind poses from FBX BindPose objects to FBX nodes. + * Must be called prior to {@link #updateWorldTransforms()}. + */ + private void applyBindPoses() { + for (FbxBindPose bindPose : bindPoses) { + Map bindPoseData = bindPose.getJmeObject(); + logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size()); + for (Map.Entry entry : bindPoseData.entrySet()) { + FbxObject obj = objectMap.get(entry.getKey()); + if (obj instanceof FbxNode) { + FbxNode node = (FbxNode) obj; + node.setWorldBindPose(entry.getValue()); + } else { + logger.log(Level.WARNING, "Bind pose can only be applied to FBX nodes. Ignoring."); + } + } + } + } + + /** + * Updates world transforms and bind poses for the FBX scene graph. + */ + private void updateWorldTransforms() { + FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT); + fbxRoot.updateWorldTransforms(null, null); + } + + private void constructAnimations() { + // In FBX, animation are not attached to any root. + // They are implicitly global. + // So, we need to use hueristics to find which node(s) + // an animation is associated with, so we can create the AnimControl + // in the appropriate location in the scene. + Map pairs = new HashMap(); + for (FbxAnimStack stack : animStacks) { + for (FbxAnimLayer layer : stack.getLayers()) { + for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) { + for (Map.Entry nodePropertyEntry : curveNode.getInfluencedNodeProperties().entrySet()) { + FbxToJmeTrack lookupPair = new FbxToJmeTrack(); + lookupPair.animStack = stack; + lookupPair.animLayer = layer; + lookupPair.node = nodePropertyEntry.getKey(); + + // Find if this pair is already stored + FbxToJmeTrack storedPair = pairs.get(lookupPair); + if (storedPair == null) { + // If not, store it. + storedPair = lookupPair; + pairs.put(storedPair, storedPair); + } + + String property = nodePropertyEntry.getValue(); + storedPair.animCurves.put(property, curveNode); + } + } + } + } + + // At this point we can construct the animation for all pairs ... + for (FbxToJmeTrack pair : pairs.values()) { + String animName = pair.animStack.getName(); + float duration = pair.animStack.getDuration(); + + System.out.println("ANIMATION: " + animName + ", duration = " + duration); + System.out.println("NODE: " + pair.node.getName()); + + duration = pair.getDuration(); + + if (pair.node instanceof FbxLimbNode) { + // Find the spatial that has the skeleton for this limb. + FbxLimbNode limbNode = (FbxLimbNode) pair.node; + Bone bone = limbNode.getJmeBone(); + Spatial jmeSpatial = limbNode.getSkeletonHolder().getJmeObject(); + Skeleton skeleton = limbNode.getSkeletonHolder().getJmeSkeleton(); + + // Get the animation control (create if missing). + AnimControl animControl = jmeSpatial.getControl(AnimControl.class); + if (animControl.getSkeleton() != skeleton) { + throw new UnsupportedOperationException(); + } + + // Get the animation (create if missing). + Animation anim = animControl.getAnim(animName); + if (anim == null) { + anim = new Animation(animName, duration); + animControl.addAnim(anim); + } + + // Find the bone index from the spatial's skeleton. + int boneIndex = skeleton.getBoneIndex(bone); + + // Generate the bone track. + BoneTrack bt = pair.toJmeBoneTrack(boneIndex, bone.getBindInverseTransform()); + + // Add the bone track to the animation. + anim.addTrack(bt); + } else { + // Create the spatial animation + Animation anim = new Animation(animName, duration); + anim.setTracks(new Track[]{ pair.toJmeSpatialTrack() }); + + // Get the animation control (create if missing). + Spatial jmeSpatial = pair.node.getJmeObject(); + AnimControl animControl = jmeSpatial.getControl(AnimControl.class); + + if (animControl == null) { + animControl = new AnimControl(null); + jmeSpatial.addControl(animControl); + } + + // Add the spatial animation + animControl.addAnim(anim); + } + } + } + + private void constructSkeletons() { + FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT); + FbxNode.createSkeletons(fbxRoot); + } + + private Spatial constructSceneGraph() { + // Acquire the implicit root object. + FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT); + + // Convert it into a jME3 scene + Node jmeRoot = (Node) FbxNode.createScene(fbxRoot); + + // Fix the name (will probably be set to something like "-node") + jmeRoot.setName(sceneName + "-scene"); + + return jmeRoot; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java index e767763e1..491301c22 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java @@ -73,9 +73,9 @@ import com.jme3.scene.Mesh.Mode; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval; -import com.jme3.scene.plugins.fbx.file.FBXElement; -import com.jme3.scene.plugins.fbx.file.FBXFile; -import com.jme3.scene.plugins.fbx.file.FBXReader; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.file.FbxFile; +import com.jme3.scene.plugins.fbx.file.FbxReader; import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; @@ -165,8 +165,8 @@ public class SceneLoader implements AssetLoader { private void loadScene(InputStream stream) throws IOException { logger.log(Level.FINE, "Loading scene {0}", sceneFilename); long startTime = System.currentTimeMillis(); - FBXFile scene = FBXReader.readFBX(stream); - for(FBXElement e : scene.rootElements) { + FbxFile scene = FbxReader.readFBX(stream); + for(FbxElement e : scene.rootElements) { if(e.id.equals("GlobalSettings")) loadGlobalSettings(e); else if(e.id.equals("Objects")) @@ -178,10 +178,10 @@ public class SceneLoader implements AssetLoader { logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime); } - private void loadGlobalSettings(FBXElement element) { - for(FBXElement e : element.children) { + private void loadGlobalSettings(FbxElement element) { + for(FbxElement e : element.children) { if(e.id.equals("Properties70")) { - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("P")) { String propName = (String) e2.properties.get(0); if(propName.equals("UnitScaleFactor")) @@ -197,8 +197,8 @@ public class SceneLoader implements AssetLoader { } } - private void loadObjects(FBXElement element) { - for(FBXElement e : element.children) { + private void loadObjects(FbxElement element) { + for(FbxElement e : element.children) { if(e.id.equals("Geometry")) loadGeometry(e); else if(e.id.equals("Material")) @@ -222,12 +222,12 @@ public class SceneLoader implements AssetLoader { } } - private void loadGeometry(FBXElement element) { + private void loadGeometry(FbxElement element) { long id = (Long) element.properties.get(0); String type = (String) element.properties.get(2); if(type.equals("Mesh")) { MeshData data = new MeshData(); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("Vertices")) data.vertices = (double[]) e.properties.get(0); else if(e.id.equals("PolygonVertexIndex")) @@ -236,7 +236,7 @@ public class SceneLoader implements AssetLoader { //else if(e.id.equals("Edges")) // data.edges = (int[]) e.properties.get(0); else if(e.id.equals("LayerElementNormal")) - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("MappingInformationType")) { data.normalsMapping = (String) e2.properties.get(0); if(!data.normalsMapping.equals("ByVertice") && !data.normalsMapping.equals("ByPolygonVertex")) @@ -249,7 +249,7 @@ public class SceneLoader implements AssetLoader { data.normals = (double[]) e2.properties.get(0); } else if(e.id.equals("LayerElementTangent")) - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("MappingInformationType")) { data.tangentsMapping = (String) e2.properties.get(0); if(!data.tangentsMapping.equals("ByVertice") && !data.tangentsMapping.equals("ByPolygonVertex")) @@ -262,7 +262,7 @@ public class SceneLoader implements AssetLoader { data.tangents = (double[]) e2.properties.get(0); } else if(e.id.equals("LayerElementBinormal")) - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("MappingInformationType")) { data.binormalsMapping = (String) e2.properties.get(0); if(!data.binormalsMapping.equals("ByVertice") && !data.binormalsMapping.equals("ByPolygonVertex")) @@ -275,7 +275,7 @@ public class SceneLoader implements AssetLoader { data.binormals = (double[]) e2.properties.get(0); } else if(e.id.equals("LayerElementUV")) - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("MappingInformationType")) { data.uvMapping = (String) e2.properties.get(0); if(!data.uvMapping.equals("ByPolygonVertex")) @@ -291,7 +291,7 @@ public class SceneLoader implements AssetLoader { } // TODO smoothing is not used now //else if(e.id.equals("LayerElementSmoothing")) - // for(FBXElement e2 : e.children) { + // for(FbxElement e2 : e.children) { // if(e2.id.equals("MappingInformationType")) { // data.smoothingMapping = (String) e2.properties.get(0); // if(!data.smoothingMapping.equals("ByEdge")) @@ -304,7 +304,7 @@ public class SceneLoader implements AssetLoader { // data.smoothing = (int[]) e2.properties.get(0); // } else if(e.id.equals("LayerElementMaterial")) - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("MappingInformationType")) { data.materialsMapping = (String) e2.properties.get(0); if(!data.materialsMapping.equals("AllSame")) @@ -321,18 +321,18 @@ public class SceneLoader implements AssetLoader { } } - private void loadMaterial(FBXElement element) { + private void loadMaterial(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); if(type.equals("")) { MaterialData data = new MaterialData(); data.name = path.substring(0, path.indexOf(0)); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("ShadingModel")) { data.shadingModel = (String) e.properties.get(0); } else if(e.id.equals("Properties70")) { - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("P")) { String propName = (String) e2.properties.get(0); if(propName.equals("AmbientColor")) { @@ -368,16 +368,16 @@ public class SceneLoader implements AssetLoader { } } - private void loadModel(FBXElement element) { + private void loadModel(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); ModelData data = new ModelData(); data.name = path.substring(0, path.indexOf(0)); data.type = type; - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("Properties70")) { - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("P")) { String propName = (String) e2.properties.get(0); if(propName.equals("Lcl Translation")) { @@ -408,17 +408,17 @@ public class SceneLoader implements AssetLoader { modelDataMap.put(id, data); } - private void loadPose(FBXElement element) { + private void loadPose(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); if(type.equals("BindPose")) { BindPoseData data = new BindPoseData(); data.name = path.substring(0, path.indexOf(0)); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("PoseNode")) { NodeTransformData item = new NodeTransformData(); - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("Node")) item.nodeId = (Long) e2.properties.get(0); else if(e2.id.equals("Matrix")) @@ -431,14 +431,14 @@ public class SceneLoader implements AssetLoader { } } - private void loadTexture(FBXElement element) { + private void loadTexture(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); if(type.equals("")) { TextureData data = new TextureData(); data.name = path.substring(0, path.indexOf(0)); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("Type")) data.bindType = (String) e.properties.get(0); else if(e.id.equals("FileName")) @@ -448,14 +448,14 @@ public class SceneLoader implements AssetLoader { } } - private void loadImage(FBXElement element) { + private void loadImage(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); if(type.equals("Clip")) { ImageData data = new ImageData(); data.name = path.substring(0, path.indexOf(0)); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("Type")) data.type = (String) e.properties.get(0); else if(e.id.equals("FileName")) @@ -471,19 +471,19 @@ public class SceneLoader implements AssetLoader { } } - private void loadDeformer(FBXElement element) { + private void loadDeformer(FbxElement element) { long id = (Long) element.properties.get(0); String type = (String) element.properties.get(2); if(type.equals("Skin")) { SkinData skinData = new SkinData(); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("SkinningType")) skinData.type = (String) e.properties.get(0); } skinMap.put(id, skinData); } else if(type.equals("Cluster")) { ClusterData clusterData = new ClusterData(); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("Indexes")) clusterData.indexes = (int[]) e.properties.get(0); else if(e.id.equals("Weights")) @@ -497,7 +497,7 @@ public class SceneLoader implements AssetLoader { } } - private void loadAnimLayer(FBXElement element) { + private void loadAnimLayer(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); @@ -508,12 +508,12 @@ public class SceneLoader implements AssetLoader { } } - private void loadAnimCurve(FBXElement element) { + private void loadAnimCurve(FbxElement element) { long id = (Long) element.properties.get(0); String type = (String) element.properties.get(2); if(type.equals("")) { AnimCurveData data = new AnimCurveData(); - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("KeyTime")) data.keyTimes = (long[]) e.properties.get(0); else if(e.id.equals("KeyValueFloat")) @@ -523,15 +523,15 @@ public class SceneLoader implements AssetLoader { } } - private void loadAnimNode(FBXElement element) { + private void loadAnimNode(FbxElement element) { long id = (Long) element.properties.get(0); String path = (String) element.properties.get(1); String type = (String) element.properties.get(2); if(type.equals("")) { Double x = null, y = null, z = null; - for(FBXElement e : element.children) { + for(FbxElement e : element.children) { if(e.id.equals("Properties70")) { - for(FBXElement e2 : e.children) { + for(FbxElement e2 : e.children) { if(e2.id.equals("P")) { String propName = (String) e2.properties.get(0); if(propName.equals("d|X")) @@ -554,8 +554,8 @@ public class SceneLoader implements AssetLoader { } } - private void loadConnections(FBXElement element) { - for(FBXElement e : element.children) { + private void loadConnections(FbxElement element) { + for(FbxElement e : element.children) { if(e.id.equals("C")) { String type = (String) e.properties.get(0); long objId, refId; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java new file mode 100644 index 000000000..d266dcf95 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; + +public class FbxAnimCurve extends FbxObject { + + private long[] keyTimes; + private float[] keyValues; + + public FbxAnimCurve(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + + for (FbxElement e : element.children) { + if (e.id.equals("KeyTime")) { + keyTimes = (long[]) e.properties.get(0); + } else if (e.id.equals("KeyValueFloat")) { + keyValues = (float[]) e.properties.get(0); + } + } + + long time = -1; + for (int i = 0; i < keyTimes.length; i++) { + if (time >= keyTimes[i]) { + throw new UnsupportedOperationException("Keyframe times must be sequential, but they are not."); + } + time = keyTimes[i]; + } + } + + /** + * Get the times for the keyframes. + * @return Keyframe times. + */ + public long[] getKeyTimes() { + return keyTimes; + } + + /** + * Retrieve the curve value at the given time. + * If the curve has no data, 0 is returned. + * If the time is outside the curve, then the closest value is returned. + * If the time isn't on an exact keyframe, linear interpolation is used + * to determine the value between the keyframes at the given time. + * @param time The time to get the curve value at (in FBX time units). + * @return The value at the given time. + */ + public float getValueAtTime(long time) { + if (keyTimes.length == 0) { + return 0; + } + + // If the time is outside the range, + // we just return the closest value. (No extrapolation) + if (time <= keyTimes[0]) { + return keyValues[0]; + } else if (time >= keyTimes[keyTimes.length - 1]) { + return keyValues[keyValues.length - 1]; + } + + + + int startFrame = 0; + int endFrame = 1; + int lastFrame = keyTimes.length - 1; + + for (int i = 0; i < lastFrame && keyTimes[i] < time; ++i) { + startFrame = i; + endFrame = i + 1; + } + + long keyTime1 = keyTimes[startFrame]; + float keyValue1 = keyValues[startFrame]; + long keyTime2 = keyTimes[endFrame]; + float keyValue2 = keyValues[endFrame]; + + if (keyTime2 == time) { + return keyValue2; + } + + long prevToNextDelta = keyTime2 - keyTime1; + long prevToCurrentDelta = time - keyTime1; + float lerpAmount = (float)prevToCurrentDelta / prevToNextDelta; + + return FastMath.interpolateLinear(lerpAmount, keyValue1, keyValue2); + } + + @Override + protected Object toJmeObject() { + // An AnimCurve has no jME3 representation. + // The parent AnimCurveNode is responsible to create the jME3 + // representation. + throw new UnsupportedOperationException("No jME3 object conversion available"); + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java new file mode 100644 index 000000000..e8dbe7fc0 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.node.FbxNode; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxAnimCurveNode extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxAnimCurveNode.class.getName()); + + private final Map influencedNodePropertiesMap = new HashMap(); + private final Map propertyToCurveMap = new HashMap(); + private final Map propertyToDefaultMap = new HashMap(); + + public FbxAnimCurveNode(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + for (FbxElement prop : element.getFbxProperties()) { + String propName = (String) prop.properties.get(0); + String propType = (String) prop.properties.get(1); + if (propType.equals("Number")) { + float propValue = ((Double) prop.properties.get(4)).floatValue(); + propertyToDefaultMap.put(propName, propValue); + } + } + } + + public void addInfluencedNode(FbxNode node, String property) { + influencedNodePropertiesMap.put(node, property); + } + + public Map getInfluencedNodeProperties() { + return influencedNodePropertiesMap; + } + + public Collection getCurves() { + return propertyToCurveMap.values(); + } + + public Vector3f getVector3Value(long time) { + Vector3f value = new Vector3f(); + FbxAnimCurve xCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X); + FbxAnimCurve yCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y); + FbxAnimCurve zCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z); + Float xDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X); + Float yDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y); + Float zDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z); + value.x = xCurve != null ? xCurve.getValueAtTime(time) : xDefault; + value.y = yCurve != null ? yCurve.getValueAtTime(time) : yDefault; + value.z = zCurve != null ? zCurve.getValueAtTime(time) : zDefault; + return value; + } + + /** + * Converts the euler angles from {@link #getVector3Value(long)} to + * a quaternion rotation. + * @param time Time at which to get the euler angles. + * @return The rotation at time + */ + public Quaternion getQuaternionValue(long time) { + Vector3f eulerAngles = getVector3Value(time); + System.out.println("\tT: " + time + ". Rotation: " + + eulerAngles.x + ", " + + eulerAngles.y + ", " + eulerAngles.z); + Quaternion q = new Quaternion(); + q.fromAngles(eulerAngles.x * FastMath.DEG_TO_RAD, + eulerAngles.y * FastMath.DEG_TO_RAD, + eulerAngles.z * FastMath.DEG_TO_RAD); + return q; + } + + @Override + protected Object toJmeObject() { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + if (!(object instanceof FbxAnimCurve)) { + unsupportedConnectObjectProperty(object, property); + } + if (!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_X) && + !property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Y) && + !property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Z) && + !property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_VISIBILITY)) { + logger.log(Level.WARNING, "Animating the dimension ''{0}'' is not " + + "supported yet. Ignoring.", property); + return; + } + + if (propertyToCurveMap.containsKey(property)) { + throw new UnsupportedOperationException("!"); + } + + propertyToCurveMap.put(property, (FbxAnimCurve) object); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java new file mode 100644 index 000000000..872626615 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +public class FbxAnimLayer extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxAnimLayer.class.getName()); + + private final List animCurves = new ArrayList(); + + public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + // No known properties for layers.. + // Also jME3 doesn't support multiple layers anyway. + } + + public List getAnimationCurveNodes() { + return Collections.unmodifiableList(animCurves); + } + + @Override + protected Object toJmeObject() { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObject(FbxObject object) { + if (!(object instanceof FbxAnimCurveNode)) { + unsupportedConnectObject(object); + } + + animCurves.add((FbxAnimCurveNode) object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } +} + \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java new file mode 100644 index 000000000..ea7dbb814 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxAnimStack extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxAnimStack.class.getName()); + + private float duration; + private FbxAnimLayer layer0; + + public FbxAnimStack(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + for (FbxElement child : element.getFbxProperties()) { + String propName = (String) child.properties.get(0); + if (propName.equals("LocalStop")) { + long durationLong = (Long)child.properties.get(4); + duration = (float) (durationLong * FbxAnimUtil.SECONDS_PER_UNIT); + } + } + } + +// /** +// * Finds out which FBX nodes this animation is going to influence. +// * +// * @return A list of FBX nodes that the stack's curves are influencing. +// */ +// public Set getInfluencedNodes() { +// HashSet influencedNodes = new HashSet(); +// if (layer0 == null) { +// return influencedNodes; +// } +// for (FbxAnimCurveNode curveNode : layer0.getAnimationCurveNodes()) { +// influencedNodes.addAll(curveNode.getInfluencedNodes()); +// } +// return influencedNodes; +// } + + public float getDuration() { + return duration; + } + + public FbxAnimLayer[] getLayers() { + return new FbxAnimLayer[]{ layer0 }; + } + + @Override + protected Object toJmeObject() { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObject(FbxObject object) { + if (!(object instanceof FbxAnimLayer)) { + unsupportedConnectObject(object); + } + + if (layer0 != null) { + logger.log(Level.WARNING, "jME3 does not support layered animation. " + + "Only first layer has been loaded."); + return; + } + + layer0 = (FbxAnimLayer) object; + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java new file mode 100644 index 000000000..c376757de --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +public class FbxAnimUtil { + /** + * Conversion factor from FBX animation time unit to seconds. + */ + public static final double SECONDS_PER_UNIT = 1 / 46186158000d; + + public static final String CURVE_NODE_PROPERTY_X = "d|X"; + public static final String CURVE_NODE_PROPERTY_Y = "d|Y"; + public static final String CURVE_NODE_PROPERTY_Z = "d|Z"; + public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility"; +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java new file mode 100644 index 000000000..78d37c74e --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.math.Matrix4f; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.file.FbxId; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.util.HashMap; +import java.util.Map; + +public class FbxBindPose extends FbxObject> { + + private final Map bindPose = new HashMap(); + + public FbxBindPose(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + for (FbxElement child : element.children) { + if (!child.id.equals("PoseNode")) { + continue; + } + + FbxId node = null; + float[] matData = null; + + for (FbxElement e : child.children) { + if (e.id.equals("Node")) { + node = FbxId.create(e.properties.get(0)); + } else if (e.id.equals("Matrix")) { + double[] matDataDoubles = (double[]) e.properties.get(0); + + if (matDataDoubles.length != 16) { + // corrupt + throw new UnsupportedOperationException("Bind pose matrix " + + "must have 16 doubles, but it has " + + matDataDoubles.length + ". Data is corrupt"); + } + + matData = new float[16]; + for (int i = 0; i < matDataDoubles.length; i++) { + matData[i] = (float) matDataDoubles[i]; + } + } + } + + if (node != null && matData != null) { + Matrix4f matrix = new Matrix4f(matData); + bindPose.put(node, matrix); + } + } + } + + @Override + protected Map toJmeObject() { + return bindPose; + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java new file mode 100644 index 000000000..3065ce88c --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxCluster extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxCluster.class.getName()); + + private int[] indexes; + private double[] weights; + private FbxLimbNode limb; + + public FbxCluster(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + for (FbxElement e : element.children) { + if (e.id.equals("Indexes")) { + indexes = (int[]) e.properties.get(0); + } else if (e.id.equals("Weights")) { + weights = (double[]) e.properties.get(0); + } + } + } + + public int[] getVertexIndices() { + return indexes; + } + + public double[] getWeights() { + return weights; + } + + public FbxLimbNode getLimb() { + return limb; + } + + @Override + protected Object toJmeObject() { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObject(FbxObject object) { + if (object instanceof FbxLimbNode) { + if (limb != null) { + logger.log(Level.WARNING, "This cluster already has a limb attached. Ignoring."); + return; + } + limb = (FbxLimbNode) object; + } else { + unsupportedConnectObject(object); + } + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java new file mode 100644 index 000000000..4b41b4f47 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.node.FbxNode; +import java.util.ArrayList; +import java.util.List; + +public class FbxLimbNode extends FbxNode { + + protected FbxNode skeletonHolder; + protected Bone bone; + + public FbxLimbNode(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + private static void createBones(FbxNode skeletonHolderNode, FbxLimbNode limb, List bones) { + limb.skeletonHolder = skeletonHolderNode; + + Bone parentBone = limb.getJmeBone(); + bones.add(parentBone); + + for (FbxNode child : limb.children) { + if (child instanceof FbxLimbNode) { + FbxLimbNode childLimb = (FbxLimbNode) child; + createBones(skeletonHolderNode, childLimb, bones); + parentBone.addChild(childLimb.getJmeBone()); + } + } + } + + public static Skeleton createSkeleton(FbxNode skeletonHolderNode) { + if (skeletonHolderNode instanceof FbxLimbNode) { + throw new UnsupportedOperationException("Limb nodes cannot be skeleton holders"); + } + + List bones = new ArrayList(); + + for (FbxNode child : skeletonHolderNode.getChildren()) { + if (child instanceof FbxLimbNode) { + createBones(skeletonHolderNode, (FbxLimbNode) child, bones); + } + } + + return new Skeleton(bones.toArray(new Bone[0])); + } + + public FbxNode getSkeletonHolder() { + return skeletonHolder; + } + + public Bone getJmeBone() { + if (bone == null) { + bone = new Bone(name); + bone.setBindTransforms(jmeLocalBindPose.getTranslation(), + jmeLocalBindPose.getRotation(), + jmeLocalBindPose.getScale()); + } + return bone; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java new file mode 100644 index 000000000..7a831cf7f --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.util.ArrayList; +import java.util.List; + +public class FbxSkinDeformer extends FbxObject> { + + private final List clusters = new ArrayList(); + + public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected List toJmeObject() { + return clusters; + } + + @Override + public void connectObject(FbxObject object) { + if (object instanceof FbxCluster) { + clusters.add((FbxCluster) object); + } else { + unsupportedConnectObject(object); + } + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java new file mode 100644 index 000000000..7b7b5ee5b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.anim; + +import com.jme3.animation.BoneTrack; +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.plugins.fbx.node.FbxNode; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Maps animation stacks to influenced nodes. + * Will be used later to create jME3 tracks. + */ +public final class FbxToJmeTrack { + + public FbxAnimStack animStack; + public FbxAnimLayer animLayer; + public FbxNode node; + + // These are not used in map lookups. + public transient final Map animCurves = new HashMap(); + + public long[] getKeyTimes() { + Set keyFrameTimesSet = new HashSet(); + for (FbxAnimCurveNode curveNode : animCurves.values()) { + for (FbxAnimCurve curve : curveNode.getCurves()) { + for (long keyTime : curve.getKeyTimes()) { + keyFrameTimesSet.add(keyTime); + } + } + } + long[] keyFrameTimes = new long[keyFrameTimesSet.size()]; + int i = 0; + for (Long keyFrameTime : keyFrameTimesSet) { + keyFrameTimes[i++] = keyFrameTime; + } + Arrays.sort(keyFrameTimes); + return keyFrameTimes; + } + + /** + * Generate a {@link BoneTrack} from the animation data, for the given + * boneIndex. + * + * @param boneIndex The bone index for which track data is generated for. + * @param inverseBindPose Inverse bind pose of the bone (in world space). + * @return A BoneTrack containing the animation data, for the specified + * boneIndex. + */ + public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) { + return (BoneTrack) toJmeTrackInternal(boneIndex, inverseBindPose); + } + + public SpatialTrack toJmeSpatialTrack() { + return (SpatialTrack) toJmeTrackInternal(-1, null); + } + + public float getDuration() { + long[] keyframes = getKeyTimes(); + return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT); + } + + private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) { + Transform t = new Transform(); + t.setTranslation(translation); + t.setRotation(rotation); + if (scale != null) { + t.setScale(scale); + } + t.combineWithParent(inverseBindPose); + + t.getTranslation(translation); + t.getRotation(rotation); + if (scale != null) { + t.getScale(scale); + } + } + + private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) { + float duration = animStack.getDuration(); + + FbxAnimCurveNode translationCurve = animCurves.get("Lcl Translation"); + FbxAnimCurveNode rotationCurve = animCurves.get("Lcl Rotation"); + FbxAnimCurveNode scalingCurve = animCurves.get("Lcl Scaling"); + + long[] fbxTimes = getKeyTimes(); + float[] times = new float[fbxTimes.length]; + + // Translations / Rotations must be set on all tracks. + // (Required for jME3) + Vector3f[] translations = new Vector3f[fbxTimes.length]; + Quaternion[] rotations = new Quaternion[fbxTimes.length]; + + Vector3f[] scales = null; + if (scalingCurve != null) { + scales = new Vector3f[fbxTimes.length]; + } + + for (int i = 0; i < fbxTimes.length; i++) { + long fbxTime = fbxTimes[i]; + float time = (float) (fbxTime * FbxAnimUtil.SECONDS_PER_UNIT); + + if (time > duration) { + // Expand animation duration to fit the curve. + duration = time; + System.out.println("actual duration: " + duration); + } + + times[i] = time; + if (translationCurve != null) { + translations[i] = translationCurve.getVector3Value(fbxTime); + } else { + translations[i] = new Vector3f(); + } + if (rotationCurve != null) { + rotations[i] = rotationCurve.getQuaternionValue(fbxTime); + if (i > 0) { + if (rotations[i - 1].dot(rotations[i]) < 0) { + System.out.println("rotation will go the long way, oh noes"); + rotations[i - 1].negate(); + } + } + } else { + rotations[i] = new Quaternion(); + } + if (scalingCurve != null) { + scales[i] = scalingCurve.getVector3Value(fbxTime); + } + + if (inverseBindPose != null) { + applyInverse(translations[i], rotations[i], scales != null ? scales[i] : null, inverseBindPose); + } + } + + if (boneIndex == -1) { + return new SpatialTrack(times, translations, rotations, scales); + } else { + if (scales != null) { + return new BoneTrack(boneIndex, times, translations, rotations, scales); + } else { + return new BoneTrack(boneIndex, times, translations, rotations); + } + } + } + + @Override + public int hashCode() { + int hash = 3; + hash = 79 * hash + this.animStack.hashCode(); + hash = 79 * hash + this.animLayer.hashCode(); + hash = 79 * hash + this.node.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + final FbxToJmeTrack other = (FbxToJmeTrack) obj; + return this.node == other.node + && this.animStack == other.animStack + && this.animLayer == other.animLayer; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java similarity index 73% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java index f309a5052..00619dce7 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java @@ -37,7 +37,8 @@ import java.lang.reflect.Array; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; -import static org.omg.IOP.IORHelper.id; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Quick n' dirty dumper of FBX binary files. @@ -46,12 +47,14 @@ import static org.omg.IOP.IORHelper.id; * * @author Kirill Vainer */ -public final class FBXDump { +public final class FbxDump { + + private static final Logger logger = Logger.getLogger(FbxDump.class.getName()); private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000000000"); - private FBXDump() { } + private FbxDump() { } /** * Creates a map between object UIDs and the objects themselves. @@ -59,16 +62,17 @@ public final class FBXDump { * @param file The file to create the mappings for. * @return The UID to object map. */ - private static Map createUidToObjectMap(FBXFile file) { - Map uidToObjectMap = new HashMap(); - for (FBXElement rootElement : file.rootElements) { + private static Map createUidToObjectMap(FbxFile file) { + Map uidToObjectMap = new HashMap(); + for (FbxElement rootElement : file.rootElements) { if (rootElement.id.equals("Objects")) { - for (FBXElement fbxObj : rootElement.children) { - if (fbxObj.propertiesTypes[0] != 'L') { - continue; // error + for (FbxElement fbxObj : rootElement.children) { + FbxId uid = FbxId.getObjectId(fbxObj); + if (uid != null) { + uidToObjectMap.put(uid, fbxObj); + } else { + logger.log(Level.WARNING, "Cannot determine ID for object: {0}", fbxObj); } - Long uid = (Long) fbxObj.properties.get(0); - uidToObjectMap.put(uid, fbxObj); } } } @@ -80,8 +84,8 @@ public final class FBXDump { * * @param file the file to dump. */ - public static void dumpFBX(FBXFile file) { - dumpFBX(file, System.out); + public static void dumpFile(FbxFile file) { + dumpFile(file, System.out); } /** @@ -90,11 +94,11 @@ public final class FBXDump { * @param file the file to dump. * @param out the output stream where to output. */ - public static void dumpFBX(FBXFile file, OutputStream out) { - Map uidToObjectMap = createUidToObjectMap(file); + public static void dumpFile(FbxFile file, OutputStream out) { + Map uidToObjectMap = createUidToObjectMap(file); PrintStream ps = new PrintStream(out); - for (FBXElement rootElement : file.rootElements) { - dumpFBXElement(rootElement, ps, 0, uidToObjectMap); + for (FbxElement rootElement : file.rootElements) { + dumpElement(rootElement, ps, 0, uidToObjectMap); } } @@ -113,9 +117,9 @@ public final class FBXDump { return string.replaceAll("\u0000\u0001", "::"); } - protected static void dumpFBXProperty(String id, char propertyType, + protected static void dumpProperty(String id, char propertyType, Object property, PrintStream ps, - Map uidToObjectMap) { + Map uidToObjectMap) { switch (propertyType) { case 'S': // String @@ -125,13 +129,19 @@ public final class FBXDump { case 'R': // RAW data. byte[] bytes = (byte[]) property; - ps.print("["); - for (int j = 0; j < bytes.length; j++) { + int numToPrint = Math.min(10 * 1024, bytes.length); + ps.print("(size = "); + ps.print(bytes.length); + ps.print(") ["); + for (int j = 0; j < numToPrint; j++) { ps.print(String.format("%02X", bytes[j] & 0xff)); if (j != bytes.length - 1) { ps.print(" "); } } + if (numToPrint < bytes.length) { + ps.print(" ..."); + } ps.print("]"); break; case 'D': @@ -159,7 +169,7 @@ public final class FBXDump { // If this is a connection, decode UID into object name. if (id.equals("C")) { Long uid = (Long) property; - FBXElement element = uidToObjectMap.get(uid); + FbxElement element = uidToObjectMap.get(FbxId.create(uid)); if (element != null) { String name = (String) element.properties.get(1); ps.print("\"" + convertFBXString(name) + "\""); @@ -178,7 +188,7 @@ public final class FBXDump { int length = Array.getLength(property); for (int j = 0; j < length; j++) { Object arrayEntry = Array.get(property, j); - dumpFBXProperty(id, Character.toUpperCase(propertyType), arrayEntry, ps, uidToObjectMap); + dumpProperty(id, Character.toUpperCase(propertyType), arrayEntry, ps, uidToObjectMap); if (j != length - 1) { ps.print(","); } @@ -189,24 +199,24 @@ public final class FBXDump { } } - protected static void dumpFBXElement(FBXElement el, PrintStream ps, - int indent, Map uidToObjectMap) { + protected static void dumpElement(FbxElement el, PrintStream ps, + int indent, Map uidToObjectMap) { // 4 spaces per tab should be OK. String indentStr = indent(indent * 4); String textId = el.id; // Properties are called 'P' and connections are called 'C'. - if (el.id.equals("P")) { - textId = "Property"; - } else if (el.id.equals("C")) { - textId = "Connect"; - } +// if (el.id.equals("P")) { +// textId = "Property"; +// } else if (el.id.equals("C")) { +// textId = "Connect"; +// } ps.print(indentStr + textId + ": "); for (int i = 0; i < el.properties.size(); i++) { Object property = el.properties.get(i); char propertyType = el.propertiesTypes[i]; - dumpFBXProperty(el.id, propertyType, property, ps, uidToObjectMap); + dumpProperty(el.id, propertyType, property, ps, uidToObjectMap); if (i != el.properties.size() - 1) { ps.print(", "); } @@ -215,8 +225,8 @@ public final class FBXDump { ps.println(); } else { ps.println(" {"); - for (FBXElement childElement : el.children) { - dumpFBXElement(childElement, ps, indent + 1, uidToObjectMap); + for (FbxElement childElement : el.children) { + dumpElement(childElement, ps, indent + 1, uidToObjectMap); } ps.println(indentStr + "}"); } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java new file mode 100644 index 000000000..1a4d09d53 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2014 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.file; + +import java.util.ArrayList; +import java.util.List; + +public class FbxElement { + + public String id; + public List properties; + /* + * Y - signed short + * C - boolean + * I - signed integer + * F - float + * D - double + * L - long + * R - byte array + * S - string + * f - array of floats + * i - array of ints + * d - array of doubles + * l - array of longs + * b - array of boleans + * c - array of unsigned bytes (represented as array of ints) + */ + public char[] propertiesTypes; + public List children = new ArrayList(); + + public FbxElement(int propsCount) { + this.properties = new ArrayList(propsCount); + this.propertiesTypes = new char[propsCount]; + } + + public FbxElement getChildById(String name) { + for (FbxElement element : children) { + if (element.id.equals(name)) { + return element; + } + } + return null; + } + + public List getFbxProperties() { + List props = new ArrayList(); + FbxElement propsElement = null; + boolean legacy = false; + + for (FbxElement element : children) { + if (element.id.equals("Properties70")) { + propsElement = element; + break; + } else if (element.id.equals("Properties60")) { + legacy = true; + propsElement = element; + break; + } + } + + if (propsElement == null) { + return props; + } + + for (FbxElement prop : propsElement.children) { + if (prop.id.equals("P") || prop.id.equals("Property")) { + if (legacy) { + char[] types = new char[prop.propertiesTypes.length + 1]; + types[0] = prop.propertiesTypes[0]; + types[1] = prop.propertiesTypes[0]; + System.arraycopy(prop.propertiesTypes, 1, types, 2, types.length - 2); + + List values = new ArrayList(prop.properties); + values.add(1, values.get(0)); + + FbxElement dummyProp = new FbxElement(types.length); + dummyProp.children = prop.children; + dummyProp.id = prop.id; + dummyProp.propertiesTypes = types; + dummyProp.properties = values; + props.add(dummyProp); + } else { + props.add(prop); + } + } + } + + return props; + } + + @Override + public String toString() { + return "FBXElement[id=" + id + ", numProps=" + properties.size() + ", numChildren=" + children.size() + "]"; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXFile.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java similarity index 87% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXFile.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java index ba81f1a74..9435b4c4e 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXFile.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java @@ -34,9 +34,13 @@ package com.jme3.scene.plugins.fbx.file; import java.util.ArrayList; import java.util.List; -public class FBXFile { +public class FbxFile { - public List rootElements = new ArrayList(); + public List rootElements = new ArrayList(); public long version; + @Override + public String toString() { + return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]"; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java new file mode 100644 index 000000000..e3c753052 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.file; + +public abstract class FbxId { + + public static final FbxId ROOT = new LongFbxId(0); + + protected FbxId() { } + + private static final class StringFbxId extends FbxId { + + private final String id; + + public StringFbxId(String id) { + this.id = id; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != StringFbxId.class) { + return false; + } + return this.id.equals(((StringFbxId) obj).id); + } + + @Override + public String toString() { + return id; + } + + @Override + public boolean isNull() { + return id.equals("Scene\u0000\u0001Model"); + } + } + + private static final class LongFbxId extends FbxId { + + private final long id; + + public LongFbxId(long id) { + this.id = id; + } + + @Override + public int hashCode() { + return (int) (this.id ^ (this.id >>> 32)); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != LongFbxId.class) { + return false; + } + return this.id == ((LongFbxId) obj).id; + } + + @Override + public boolean isNull() { + return id == 0; + } + + @Override + public String toString() { + return Long.toString(id); + } + } + + public abstract boolean isNull(); + + public static FbxId create(Object obj) { + if (obj instanceof Long) { + return new LongFbxId((Long)obj); + } else if (obj instanceof String) { + return new StringFbxId((String)obj); + } else { + throw new UnsupportedOperationException("Unsupported ID object type: " + obj.getClass()); + } + } + + public static FbxId getObjectId(FbxElement el) { + if (el.propertiesTypes.length == 2 + && el.propertiesTypes[0] == 'S' + && el.propertiesTypes[1] == 'S') { + return new StringFbxId((String) el.properties.get(0)); + } else if (el.propertiesTypes.length == 3 + && el.propertiesTypes[0] == 'L' + && el.propertiesTypes[1] == 'S' + && el.propertiesTypes[2] == 'S') { + return new LongFbxId((Long) el.properties.get(0)); + } else { + return null; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXReader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java similarity index 94% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXReader.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java index b39dfef1c..22e93d8db 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXReader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java @@ -40,8 +40,8 @@ import java.nio.ByteOrder; import java.util.Arrays; import java.util.zip.InflaterInputStream; -public class FBXReader { - +public class FbxReader { + public static final int BLOCK_SENTINEL_LENGTH = 13; public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH]; /** @@ -49,9 +49,9 @@ public class FBXReader { * "Kaydara FBX Binary\x20\x20\x00\x1a\x00" */ public static final byte[] HEAD_MAGIC = new byte[]{0x4b, 0x61, 0x79, 0x64, 0x61, 0x72, 0x61, 0x20, 0x46, 0x42, 0x58, 0x20, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x20, 0x00, 0x1a, 0x00}; - - public static FBXFile readFBX(InputStream stream) throws IOException { - FBXFile fbxFile = new FBXFile(); + + public static FbxFile readFBX(InputStream stream) throws IOException { + FbxFile fbxFile = new FbxFile(); // Read file to byte buffer so we can know current position in file ByteBuffer byteBuffer = readToByteBuffer(stream); try { @@ -61,27 +61,29 @@ public class FBXReader { // Check majic header byte[] majic = getBytes(byteBuffer, HEAD_MAGIC.length); if(!Arrays.equals(HEAD_MAGIC, majic)) - throw new IOException("Not FBX file"); + throw new IOException("Either ASCII FBX or corrupt file. " + + "Only binary FBX files are supported"); + // Read version fbxFile.version = getUInt(byteBuffer); // Read root elements while(true) { - FBXElement e = readFBXElement(byteBuffer); + FbxElement e = readFBXElement(byteBuffer); if(e == null) break; fbxFile.rootElements.add(e); } return fbxFile; } - - private static FBXElement readFBXElement(ByteBuffer byteBuffer) throws IOException { + + private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException { long endOffset = getUInt(byteBuffer); if(endOffset == 0) return null; long propCount = getUInt(byteBuffer); getUInt(byteBuffer); // Properties length unused - FBXElement element = new FBXElement((int) propCount); + FbxElement element = new FbxElement((int) propCount); element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer))); for(int i = 0; i < propCount; ++i) { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java new file mode 100644 index 000000000..518dd4134 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.material; + +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.TextureKey; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import com.jme3.texture.Image; +import com.jme3.util.PlaceholderAssets; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class FbxImage extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxImage.class.getName()); + + protected TextureKey key; + protected String type; // = "Clip" + protected String filePath; // = "C:\Whatever\Blah\Texture.png" + protected String relativeFilePath; // = "..\Blah\Texture.png" + protected byte[] content; // = null, byte[0] OR embedded image data (unknown format?) + + public FbxImage(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + if (element.propertiesTypes.length == 3) { + type = (String) element.properties.get(2); + } else { + type = (String) element.properties.get(1); + } + if (type.equals("Clip")) { + for (FbxElement e : element.children) { + if (e.id.equals("Type")) { + type = (String) e.properties.get(0); + } else if (e.id.equals("FileName")) { + filePath = (String) e.properties.get(0); + } else if (e.id.equals("RelativeFilename")) { + relativeFilePath = (String) e.properties.get(0); + } else if (e.id.equals("Content")) { + if (e.properties.size() > 0) { + byte[] storedContent = (byte[]) e.properties.get(0); + if (storedContent.length > 0) { + this.content = storedContent; + } + } + } + } + } + } + + private Image loadImageSafe(AssetManager assetManager, TextureKey texKey) { + try { + return assetManager.loadTexture(texKey).getImage(); + } catch (AssetNotFoundException ex) { + return null; + } catch (AssetLoadException ex) { + logger.log(Level.WARNING, "Error when loading image: " + texKey, ex); + return null; + } + } + + private static String getFileName(String filePath) { + // NOTE: Gotta do it this way because new File().getParent() + // will not strip forward slashes on Linux / Mac OS X. + int fwdSlashIdx = filePath.lastIndexOf("\\"); + int bkSlashIdx = filePath.lastIndexOf("/"); + + if (fwdSlashIdx != -1) { + filePath = filePath.substring(fwdSlashIdx + 1); + } else if (bkSlashIdx != -1) { + filePath = filePath.substring(bkSlashIdx + 1); + } + + return filePath; + } + + /** + * The texture key that was used to load the image. + * Only valid after {@link #getJmeObject()} has been called. + * @return the key that was used to load the image. + */ + public TextureKey getTextureKey() { + return key; + } + + @Override + protected Object toJmeObject() { + Image image = null; + String fileName = null; + String relativeFilePathJme; + + if (filePath != null) { + fileName = getFileName(filePath); + } else if (relativeFilePath != null) { + fileName = getFileName(relativeFilePath); + + } + + if (fileName != null) { + try { + // Try to load filename relative to FBX folder + key = new TextureKey(sceneFolderName + fileName); + key.setGenerateMips(true); + image = loadImageSafe(assetManager, key); + + // Try to load relative filepath relative to FBX folder + if (image == null && relativeFilePath != null) { + // Convert Windows paths to jME3 paths + relativeFilePathJme = relativeFilePath.replace('\\', '/'); + key = new TextureKey(sceneFolderName + relativeFilePathJme); + key.setGenerateMips(true); + image = loadImageSafe(assetManager, key); + } + + // Try to load embedded image + if (image == null && content != null && content.length > 0) { + key = new TextureKey(fileName); + key.setGenerateMips(true); + InputStream is = new ByteArrayInputStream(content); + image = assetManager.loadAssetFromStream(key, is).getImage(); + + // NOTE: embedded texture doesn't exist in the asset manager, + // so the texture key must not be saved. + key = null; + } + } catch (AssetLoadException ex) { + logger.log(Level.WARNING, "Error while attempting to load texture {0}:\n{1}", + new Object[]{name, ex.toString()}); + } + } + + if (image == null) { + logger.log(Level.WARNING, "Cannot locate {0} for texture {1}", new Object[]{fileName, name}); + image = PlaceholderAssets.getPlaceholderImage(assetManager); + } + + // NOTE: At this point, key will be set to the last + // attempted texture key that we attempted to load. + + return image; + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java new file mode 100644 index 000000000..9be5bf70c --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.material; + +import com.jme3.asset.AssetManager; +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.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import com.jme3.texture.Texture; +import com.jme3.texture.image.ColorSpace; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxMaterial extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxMaterial.class.getName()); + + private String shadingModel; // TODO: do we care about this? lambert just has no specular? + private final FbxMaterialProperties properties = new FbxMaterialProperties(); + + public FbxMaterial(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + if(!getSubclassName().equals("")) { + return; + } + + FbxElement shadingModelEl = element.getChildById("ShadingModel"); + if (shadingModelEl != null) { + shadingModel = (String) shadingModelEl.properties.get(0); + if (!shadingModel.equals("")) { + if (!shadingModel.equalsIgnoreCase("phong") && + !shadingModel.equalsIgnoreCase("lambert")) { + logger.log(Level.WARNING, "FBX material uses unknown shading model: {0}. " + + "Material may display incorrectly.", shadingModel); + } + } + } + + for (FbxElement child : element.getFbxProperties()) { + properties.setPropertyFromElement(child); + } + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + if (!(object instanceof FbxTexture)) { + unsupportedConnectObjectProperty(object, property); + } + + properties.setPropertyTexture(property, (FbxTexture) object); + } + + private static void multRGB(ColorRGBA color, float factor) { + color.r *= factor; + color.g *= factor; + color.b *= factor; + } + + @Override + protected Material toJmeObject() { + ColorRGBA ambient = null; + ColorRGBA diffuse = null; + ColorRGBA specular = null; + ColorRGBA transp = null; + ColorRGBA emissive = null; + float shininess = 1f; + boolean separateTexCoord = false; + + Texture diffuseMap = null; + Texture specularMap = null; + Texture normalMap = null; + Texture transpMap = null; + Texture emitMap = null; + Texture aoMap = null; + + FbxTexture fbxDiffuseMap = null; + + Object diffuseColor = properties.getProperty("DiffuseColor"); + if (diffuseColor != null) { + if (diffuseColor instanceof ColorRGBA) { + diffuse = ((ColorRGBA) diffuseColor).clone(); + } else if (diffuseColor instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) diffuseColor; + fbxDiffuseMap = tex; + diffuseMap = tex.getJmeObject(); + diffuseMap.getImage().setColorSpace(ColorSpace.sRGB); + } + } + + Object diffuseFactor = properties.getProperty("DiffuseFactor"); + if (diffuseFactor != null && diffuseFactor instanceof Float) { + float factor = (Float)diffuseFactor; + if (diffuse != null) { + multRGB(diffuse, factor); + } else { + diffuse = new ColorRGBA(factor, factor, factor, 1f); + } + } + + Object specularColor = properties.getProperty("SpecularColor"); + if (specularColor != null) { + if (specularColor instanceof ColorRGBA) { + specular = ((ColorRGBA) specularColor).clone(); + } else if (specularColor instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) specularColor; + specularMap = tex.getJmeObject(); + specularMap.getImage().setColorSpace(ColorSpace.sRGB); + } + } + + Object specularFactor = properties.getProperty("SpecularFactor"); + if (specularFactor != null && specularFactor instanceof Float) { + float factor = (Float)specularFactor; + if (specular != null) { + multRGB(specular, factor); + } else { + specular = new ColorRGBA(factor, factor, factor, 1f); + } + } + + Object transparentColor = properties.getProperty("TransparentColor"); + if (transparentColor != null) { + if (transparentColor instanceof ColorRGBA) { + transp = ((ColorRGBA) transparentColor).clone(); + } else if (transparentColor instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) transparentColor; + transpMap = tex.getJmeObject(); + transpMap.getImage().setColorSpace(ColorSpace.sRGB); + } + } + + Object transparencyFactor = properties.getProperty("TransparencyFactor"); + if (transparencyFactor != null && transparencyFactor instanceof Float) { + float factor = (Float)transparencyFactor; + if (transp != null) { + transp.a *= factor; + } else { + transp = new ColorRGBA(1f, 1f, 1f, factor); + } + } + + Object emissiveColor = properties.getProperty("EmissiveColor"); + if (emissiveColor != null) { + if (emissiveColor instanceof ColorRGBA) { + emissive = ((ColorRGBA)emissiveColor).clone(); + } else if (emissiveColor instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) emissiveColor; + emitMap = tex.getJmeObject(); + emitMap.getImage().setColorSpace(ColorSpace.sRGB); + } + } + + Object emissiveFactor = properties.getProperty("EmissiveFactor"); + if (emissiveFactor != null && emissiveFactor instanceof Float) { + float factor = (Float)emissiveFactor; + if (emissive != null) { + multRGB(emissive, factor); + } else { + emissive = new ColorRGBA(factor, factor, factor, 1f); + } + } + + Object ambientColor = properties.getProperty("AmbientColor"); + if (ambientColor != null && ambientColor instanceof ColorRGBA) { + ambient = ((ColorRGBA)ambientColor).clone(); + } + + Object ambientFactor = properties.getProperty("AmbientFactor"); + if (ambientFactor != null && ambientFactor instanceof Float) { + float factor = (Float)ambientFactor; + if (ambient != null) { + multRGB(ambient, factor); + } else { + ambient = new ColorRGBA(factor, factor, factor, 1f); + } + } + + Object shininessFactor = properties.getProperty("Shininess"); + if (shininessFactor != null) { + if (shininessFactor instanceof Float) { + shininess = (Float) shininessFactor; + } else if (shininessFactor instanceof FbxTexture) { + // TODO: support shininess textures + } + } + + Object bumpNormal = properties.getProperty("NormalMap"); + if (bumpNormal != null) { + if (bumpNormal instanceof FbxTexture) { + // TODO: check all meshes that use this material have tangents + // otherwise shading errors occur + FbxTexture tex = (FbxTexture) bumpNormal; + normalMap = tex.getJmeObject(); + normalMap.getImage().setColorSpace(ColorSpace.Linear); + } + } + + Object aoColor = properties.getProperty("DiffuseColor2"); + if (aoColor != null) { + if (aoColor instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) aoColor; + if (tex.getUvSet() != null && fbxDiffuseMap != null) { + if (!tex.getUvSet().equals(fbxDiffuseMap.getUvSet())) { + separateTexCoord = true; + } + } + aoMap = tex.getJmeObject(); + aoMap.getImage().setColorSpace(ColorSpace.sRGB); + } + } + + // TODO: how to disable transparency from diffuse map?? Need "UseAlpha" again.. + + assert ambient == null || ambient.a == 1f; + assert diffuse == null || diffuse.a == 1f; + assert specular == null || specular.a == 1f; + assert emissive == null || emissive.a == 1f; + assert transp == null || (transp.r == 1f && transp.g == 1f && transp.b == 1f); + + // If shininess is less than 1.0, the lighting shader won't be able + // to handle it. Gotta disable specularity then. + if (shininess < 1f) { + shininess = 1f; + specular = ColorRGBA.Black; + } + + // Try to guess if we need to enable alpha blending. + // FBX does not specify this explicitly. + boolean useAlphaBlend = false; + + if (diffuseMap != null && diffuseMap == transpMap) { + // jME3 already uses alpha from diffuseMap + // (if alpha blend is enabled) + useAlphaBlend = true; + transpMap = null; + } else if (diffuseMap != null && transpMap != null && diffuseMap != transpMap) { + // TODO: potential bug here. Alpha from diffuse may + // leak unintentionally. + useAlphaBlend = true; + } else if (transpMap != null) { + // We have alpha map but no diffuse map, OK. + useAlphaBlend = true; + } + + if (transp != null && transp.a != 1f) { + // Consolidate transp into diffuse + // (jME3 doesn't use a separate alpha color) + + // TODO: potential bug here. Alpha from diffuse may + // leak unintentionally. + useAlphaBlend = true; + if (diffuse != null) { + diffuse.a = transp.a; + } else { + diffuse = transp; + } + } + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setName(name); + + // TODO: load this from FBX material. + mat.setReceivesShadows(true); + + if (useAlphaBlend) { + // No idea if this is a transparent or translucent model, gotta guess.. + mat.setTransparent(true); + mat.setFloat("AlphaDiscardThreshold", 0.01f); + mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + } + + mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); + + // Set colors. + if (ambient != null || diffuse != null || specular != null) { + // If either of those is set, we have to set them all. + // NOTE: default specular is black, unless it is set explicitly. + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", /*ambient != null ? ambient :*/ ColorRGBA.White); + mat.setColor("Diffuse", diffuse != null ? diffuse : ColorRGBA.White); + mat.setColor("Specular", specular != null ? specular : ColorRGBA.Black); + } + + if (emissive != null) { + mat.setColor("GlowColor", emissive); + } + + // Set shininess. + if (shininess > 1f) { + // Convert shininess from + // Phong (FBX shading model) to Blinn (jME3 shading model). + float blinnShininess = (shininess * 5.1f) + 1f; + mat.setFloat("Shininess", blinnShininess); + } + + // Set textures. + if (diffuseMap != null) { + mat.setTexture("DiffuseMap", diffuseMap); + } + if (specularMap != null) { + mat.setTexture("SpecularMap", specularMap); + } + if (normalMap != null) { + mat.setTexture("NormalMap", normalMap); + } + if (transpMap != null) { +// mat.setTexture("AlphaMap", transpMap); + } + if (emitMap != null) { + mat.setTexture("GlowMap", emitMap); + } + if (aoMap != null) { + mat.setTexture("LightMap", aoMap); + if (separateTexCoord) { + mat.setBoolean("SeparateTexCoord", true); + } + } + + return mat; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java new file mode 100644 index 000000000..14d23900d --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.material; + +import com.jme3.math.ColorRGBA; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxMaterialProperties { + + private static final Logger logger = Logger.getLogger(FbxMaterialProperties.class.getName()); + + private static final Map propertyMetaMap = new HashMap(); + + private final Map propertyValueMap = new HashMap(); + + private static enum Type { + Color, + Alpha, + Factor, + Texture2DOrColor, + Texture2DOrAlpha, + Texture2DOrFactor, + Texture2D, + TextureCubeMap, + Ignore; + } + + private static class FBXMaterialProperty { + private final String name; + private final Type type; + + public FBXMaterialProperty(String name, Type type) { + this.name = name; + this.type = type; + } + } + + private static boolean isValueAcceptable(Type type, Object value) { + if (type == Type.Ignore) { + return true; + } + if (value instanceof FbxTexture) { + switch (type) { + case Texture2D: + case Texture2DOrAlpha: + case Texture2DOrColor: + case Texture2DOrFactor: + return true; + } + } else if (value instanceof ColorRGBA) { + switch (type) { + case Color: + case Texture2DOrColor: + return true; + } + } else if (value instanceof Float) { + switch (type) { + case Alpha: + case Factor: + case Texture2DOrAlpha: + case Texture2DOrFactor: + return true; + } + } + + return false; + } + + private static void defineProp(String name, Type type) { + propertyMetaMap.put(name, new FBXMaterialProperty(name, type)); + } + + private static void defineAlias(String alias, String name) { + propertyMetaMap.put(alias, propertyMetaMap.get(name)); + } + + static { + // Lighting->Ambient + // TODO: Add support for AmbientMap?? + defineProp("AmbientColor", Type.Color); + defineProp("AmbientFactor", Type.Factor); + defineAlias("Ambient", "AmbientColor"); + + // Lighting->DiffuseMap/Diffuse + defineProp("DiffuseColor", Type.Texture2DOrColor); + defineProp("DiffuseFactor", Type.Factor); + defineAlias("Diffuse", "DiffuseColor"); + + // Lighting->SpecularMap/Specular + defineProp("SpecularColor", Type.Texture2DOrColor); + defineProp("SpecularFactor", Type.Factor); + defineAlias("Specular", "SpecularColor"); + + // Lighting->AlphaMap/Diffuse + defineProp("TransparentColor", Type.Texture2DOrAlpha); + + // Lighting->Diffuse + defineProp("TransparencyFactor", Type.Alpha); + defineAlias("Opacity", "TransparencyFactor"); + + // Lighting->GlowMap/GlowColor + defineProp("EmissiveColor", Type.Texture2DOrColor); + defineProp("EmissiveFactor", Type.Factor); + defineAlias("Emissive", "EmissiveColor"); + + // Lighting->Shininess + defineProp("Shininess", Type.Factor); + defineAlias("ShininessExponent", "Shininess"); + + // Lighting->NormalMap + defineProp("NormalMap", Type.Texture2D); + defineAlias("Normal", "NormalMap"); + + // Lighting->EnvMap + defineProp("ReflectionColor", Type.Texture2DOrColor); + + // Lighting->FresnelParams + defineProp("Reflectivity", Type.Factor); + defineAlias("ReflectionFactor", "Reflectivity"); + + // ShadingModel is no longer specified under Properties element. + defineProp("ShadingModel", Type.Ignore); + + // MultiLayer materials aren't supported anyway.. + defineProp("MultiLayer", Type.Ignore); + + // Not sure what this is.. NormalMap again?? + defineProp("Bump", Type.Texture2DOrColor); + + defineProp("BumpFactor", Type.Factor); + defineProp("DisplacementColor", Type.Color); + defineProp("DisplacementFactor", Type.Factor); + } + + public void setPropertyTexture(String name, FbxTexture texture) { + FBXMaterialProperty prop = propertyMetaMap.get(name); + + if (prop == null) { + logger.log(Level.WARNING, "Unknown FBX material property '{0}'", name); + return; + } + + if (propertyValueMap.get(name) instanceof FbxTexture) { + // Multiple / layered textures .. + // Just write into 2nd slot for now (maybe will use for lightmaps). + name = name + "2"; + } + + propertyValueMap.put(name, texture); + } + + public void setPropertyFromElement(FbxElement propertyElement) { + String name = (String) propertyElement.properties.get(0); + FBXMaterialProperty prop = propertyMetaMap.get(name); + + if (prop == null) { + logger.log(Level.WARNING, "Unknown FBX material property ''{0}''", name); + return; + } + + // It is either a color, alpha, or factor. + // Textures can only be set via setPropertyTexture. + + // If it is an alias, get the real name of the property. + String realName = prop.name; + + switch (prop.type) { + case Alpha: + case Factor: + case Texture2DOrFactor: + case Texture2DOrAlpha: + double value = (Double) propertyElement.properties.get(4); + propertyValueMap.put(realName, (float)value); + break; + case Color: + case Texture2DOrColor: + double x = (Double) propertyElement.properties.get(4); + double y = (Double) propertyElement.properties.get(5); + double z = (Double) propertyElement.properties.get(6); + ColorRGBA color = new ColorRGBA((float)x, (float)y, (float)z, 1f); + propertyValueMap.put(realName, color); + break; + default: + logger.log(Level.WARNING, "FBX material property ''{0}'' requires a texture.", name); + break; + } + } + + public Object getProperty(String name) { + return propertyValueMap.get(name); + } + + public static Type getPropertyType(String name) { + FBXMaterialProperty prop = propertyMetaMap.get(name); + if (prop == null) { + return null; + } else { + return prop.type; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java new file mode 100644 index 000000000..4569dad33 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.material; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.math.Vector2f; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +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.WrapAxis; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.PlaceholderAssets; + +public class FbxTexture extends FbxObject { + + private static enum AlphaSource { + None, + FromTextureAlpha, + FromTextureIntensity; + } + + private String type; + private FbxImage media; + + // TODO: not currently used. + private AlphaSource alphaSource = AlphaSource.FromTextureAlpha; + private String uvSet; + private int wrapModeU = 0, wrapModeV = 0; + private final Vector2f uvTranslation = new Vector2f(0, 0); + private final Vector2f uvScaling = new Vector2f(1, 1); + + public FbxTexture(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + public String getUvSet() { + return uvSet; + } + + @Override + protected Texture toJmeObject() { + Image image = null; + TextureKey key = null; + if (media != null) { + image = (Image) media.getJmeObject(); + key = media.getTextureKey(); + } + if (image == null) { + image = PlaceholderAssets.getPlaceholderImage(assetManager); + } + Texture2D tex = new Texture2D(image); + if (key != null) { + tex.setKey(key); + tex.setName(key.getName()); + tex.setAnisotropicFilter(key.getAnisotropy()); + } + tex.setMinFilter(MinFilter.Trilinear); + tex.setMagFilter(MagFilter.Bilinear); + if (wrapModeU == 0) { + tex.setWrap(WrapAxis.S, WrapMode.Repeat); + } + if (wrapModeV == 0) { + tex.setWrap(WrapAxis.T, WrapMode.Repeat); + } + return tex; + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + if (getSubclassName().equals("")) { + for (FbxElement e : element.children) { + if (e.id.equals("Type")) { + type = (String) e.properties.get(0); + } + /*else if (e.id.equals("FileName")) { + filename = (String) e.properties.get(0); + }*/ + } + + for (FbxElement prop : element.getFbxProperties()) { + String propName = (String) prop.properties.get(0); + if (propName.equals("AlphaSource")) { + // ??? + } else if (propName.equals("UVSet")) { + uvSet = (String) prop.properties.get(4); + } else if (propName.equals("WrapModeU")) { + wrapModeU = (Integer) prop.properties.get(4); + } else if (propName.equals("WrapModeV")) { + wrapModeV = (Integer) prop.properties.get(4); + } + } + } + } + + @Override + public void connectObject(FbxObject object) { + if (!(object instanceof FbxImage)) { + unsupportedConnectObject(object); +// } else if (media != null) { +// throw new UnsupportedOperationException("An image is already attached to this texture."); + } + + this.media = (FbxImage) object; + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java new file mode 100644 index 000000000..d1b9d3860 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.mesh; + +import com.jme3.scene.plugins.fbx.file.FbxElement; +import java.util.Collection; +import java.util.EnumMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class FbxLayer { + + private static final Logger logger = Logger.getLogger(FbxLayer.class.getName()); + + public static class FbxLayerElementRef { + FbxLayerElement.Type layerElementType; + int layerElementIndex; + FbxLayerElement layerElement; + } + + int layer; + final EnumMap references = + new EnumMap(FbxLayerElement.Type.class); + + private FbxLayer() { } + + public Object getVertexData(FbxLayerElement.Type type, int polygonIndex, + int polygonVertexIndex, int positionIndex, int edgeIndex) { + FbxLayerElementRef reference = references.get(type); + if (reference == null) { + return null; + } else { + return reference.layerElement.getVertexData(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex); + } + } + + public FbxLayerElement.Type[] getLayerElementTypes() { + FbxLayerElement.Type[] types = new FbxLayerElement.Type[references.size()]; + references.keySet().toArray(types); + return types; + } + + public void setLayerElements(Collection layerElements) { + for (FbxLayerElement layerElement : layerElements) { + FbxLayerElementRef reference = references.get(layerElement.type); + if (reference != null && reference.layerElementIndex == layerElement.index) { + reference.layerElement = layerElement; + } + } + } + + public static FbxLayer fromElement(FbxElement element) { + FbxLayer layer = new FbxLayer(); + layer.layer = (Integer)element.properties.get(0); + next_element: for (FbxElement child : element.children) { + if (!child.id.equals("LayerElement")) { + continue; + } + FbxLayerElementRef ref = new FbxLayerElementRef(); + for (FbxElement child2 : child.children) { + if (child2.id.equals("Type")) { + String layerElementTypeStr = (String) child2.properties.get(0); + layerElementTypeStr = layerElementTypeStr.substring("LayerElement".length()); + try { + ref.layerElementType = FbxLayerElement.Type.valueOf(layerElementTypeStr); + } catch (IllegalArgumentException ex) { + logger.log(Level.WARNING, "Unsupported layer type: {0}. Ignoring.", layerElementTypeStr); + continue next_element; + } + } else if (child2.id.equals("TypedIndex")) { + ref.layerElementIndex = (Integer) child2.properties.get(0); + } + } + layer.references.put(ref.layerElementType, ref); + } + return layer; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java new file mode 100644 index 000000000..bffd94c65 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.mesh; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxLayerElement { + + private static final Logger logger = Logger.getLogger(FbxLayerElement.class.getName()); + + public enum Type { + Position, // Vector3f (isn't actually defined in FBX) + BoneIndex, // List (isn't actually defined in FBX) + BoneWeight, // List isn't actually defined in FBX) + Normal, // Vector3f + Binormal, // Vector3f + Tangent, // Vector3f + UV, // Vector2f + TransparentUV, // Vector2f + Color, // ColorRGBA + Material, // Integer + Smoothing, // Integer + Visibility, // Integer + Texture, // ??? (FBX 6.x) + PolygonGroup, // ??? (FBX 6.x) + NormalMapTextures, // ??? (FBX 6.x) + SpecularFactorUV, // ??? (FBX 6.x) + NormalMapUV, // ??? (FBX 6.x) + SpecularFactorTextures, // ??? (FBX 6.x) + + } + + public enum MappingInformationType { + NoMappingInformation, + AllSame, + ByPolygonVertex, + ByVertex, + ByPolygon, + ByEdge; + } + + public enum ReferenceInformationType { + Direct, + IndexToDirect; + } + + public enum TextureBlendMode { + Translucent; + } + + private static final Set indexTypes = new HashSet(); + + static { + indexTypes.add("UVIndex"); + indexTypes.add("NormalsIndex"); + indexTypes.add("TangentsIndex"); + indexTypes.add("BinormalsIndex"); + indexTypes.add("Smoothing"); + indexTypes.add("Materials"); + indexTypes.add("TextureId"); + indexTypes.add("ColorIndex"); + indexTypes.add("PolygonGroup"); + } + + int index; + Type type; + ReferenceInformationType refInfoType; + MappingInformationType mapInfoType; + String name = ""; + Object[] data; + int[] dataIndices; + + private FbxLayerElement() { } + + public String toString() { + return "LayerElement[type=" + type + ", layer=" + index + + ", mapInfoType=" + mapInfoType + ", refInfoType=" + refInfoType + "]"; + } + + private Object getVertexDataIndexToDirect(int polygonIndex, int polygonVertexIndex, + int positionIndex, int edgeIndex) { + switch (mapInfoType) { + case AllSame: return data[dataIndices[0]]; + case ByPolygon: return data[dataIndices[polygonIndex]]; + case ByPolygonVertex: return data[dataIndices[polygonVertexIndex]]; + case ByVertex: return data[dataIndices[positionIndex]]; + case ByEdge: return data[dataIndices[edgeIndex]]; + default: throw new UnsupportedOperationException(); + } + } + + private Object getVertexDataDirect(int polygonIndex, int polygonVertexIndex, + int positionIndex, int edgeIndex) { + switch (mapInfoType) { + case AllSame: return data[0]; + case ByPolygon: return data[polygonIndex]; + case ByPolygonVertex: return data[polygonVertexIndex]; + case ByVertex: return data[positionIndex]; + case ByEdge: return data[edgeIndex]; + default: throw new UnsupportedOperationException(); + } + } + + public Object getVertexData(int polygonIndex, int polygonVertexIndex, int positionIndex, int edgeIndex) { + switch (refInfoType) { + case Direct: return getVertexDataDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex); + case IndexToDirect: return getVertexDataIndexToDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex); + default: return null; + } + } + + public static FbxLayerElement fromPositions(double[] positionData) { + FbxLayerElement layerElement = new FbxLayerElement(); + layerElement.index = -1; + layerElement.name = ""; + layerElement.type = Type.Position; + layerElement.mapInfoType = MappingInformationType.ByVertex; + layerElement.refInfoType = ReferenceInformationType.Direct; + layerElement.data = toVector3(positionData); + layerElement.dataIndices = null; + return layerElement; + } + + public static FbxLayerElement fromElement(FbxElement element) { + FbxLayerElement layerElement = new FbxLayerElement(); + if (!element.id.startsWith("LayerElement")) { + throw new IllegalArgumentException("Not a layer element"); + } + layerElement.index = (Integer)element.properties.get(0); + + String elementType = element.id.substring("LayerElement".length()); + try { + layerElement.type = Type.valueOf(elementType); + } catch (IllegalArgumentException ex) { + logger.log(Level.WARNING, "Unsupported layer element: {0}. Ignoring.", elementType); + } + for (FbxElement child : element.children) { + if (child.id.equals("MappingInformationType")) { + String mapInfoTypeVal = (String) child.properties.get(0); + if (mapInfoTypeVal.equals("ByVertice")) { + mapInfoTypeVal = "ByVertex"; + } + layerElement.mapInfoType = MappingInformationType.valueOf(mapInfoTypeVal); + } else if (child.id.equals("ReferenceInformationType")) { + String refInfoTypeVal = (String) child.properties.get(0); + if (refInfoTypeVal.equals("Index")) { + refInfoTypeVal = "IndexToDirect"; + } + layerElement.refInfoType = ReferenceInformationType.valueOf(refInfoTypeVal); + } else if (child.id.equals("Normals") || child.id.equals("Tangents") || child.id.equals("Binormals")) { + layerElement.data = toVector3(FbxMeshUtil.getDoubleArray(child)); + } else if (child.id.equals("Colors")) { + layerElement.data = toColorRGBA(FbxMeshUtil.getDoubleArray(child)); + } else if (child.id.equals("UV")) { + layerElement.data = toVector2(FbxMeshUtil.getDoubleArray(child)); + } else if (indexTypes.contains(child.id)) { + layerElement.dataIndices = FbxMeshUtil.getIntArray(child); + } else if (child.id.equals("Name")) { + layerElement.name = (String) child.properties.get(0); + } + } + if (layerElement.data == null) { + // For Smoothing / Materials, data = dataIndices + layerElement.refInfoType = ReferenceInformationType.Direct; + layerElement.data = new Integer[layerElement.dataIndices.length]; + for (int i = 0; i < layerElement.data.length; i++) { + layerElement.data[i] = layerElement.dataIndices[i]; + } + layerElement.dataIndices = null; + } + return layerElement; + } + + static Vector3f[] toVector3(double[] data) { + Vector3f[] vectors = new Vector3f[data.length / 3]; + for (int i = 0; i < vectors.length; i++) { + float x = (float) data[i * 3]; + float y = (float) data[i * 3 + 1]; + float z = (float) data[i * 3 + 2]; + vectors[i] = new Vector3f(x, y, z); + } + return vectors; + } + + static Vector2f[] toVector2(double[] data) { + Vector2f[] vectors = new Vector2f[data.length / 2]; + for (int i = 0; i < vectors.length; i++) { + float x = (float) data[i * 2]; + float y = (float) data[i * 2 + 1]; + vectors[i] = new Vector2f(x, y); + } + return vectors; + } + + static ColorRGBA[] toColorRGBA(double[] data) { + ColorRGBA[] colors = new ColorRGBA[data.length / 4]; + for (int i = 0; i < colors.length; i++) { + float r = (float) data[i * 4]; + float g = (float) data[i * 4 + 1]; + float b = (float) data[i * 4 + 2]; + float a = (float) data[i * 4 + 3]; + colors[i] = new ColorRGBA(r, g, b, a); + } + return colors; + } +} + diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java new file mode 100644 index 000000000..5dd911bed --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.mesh; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.asset.AssetManager; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.plugins.IrUtils; +import com.jme3.scene.plugins.IrBoneWeightIndex; +import com.jme3.scene.plugins.IrMesh; +import com.jme3.scene.plugins.IrPolygon; +import com.jme3.scene.plugins.IrVertex; +import com.jme3.scene.plugins.fbx.anim.FbxCluster; +import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; +import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import com.jme3.scene.plugins.fbx.node.FbxNodeAttribute; +import com.jme3.util.IntMap; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class FbxMesh extends FbxNodeAttribute> { + + private static final Logger logger = Logger.getLogger(FbxMesh.class.getName()); + + private FbxPolygon[] polygons; + private int[] edges; + private FbxLayerElement[] layerElements; + private Vector3f[] positions; + private FbxLayer[] layers; + + private ArrayList[] boneIndices; + private ArrayList[] boneWeights; + + private FbxSkinDeformer skinDeformer; + + public FbxMesh(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + + List layerElementsList = new ArrayList(); + List layersList = new ArrayList(); + + for (FbxElement e : element.children) { + if (e.id.equals("Vertices")) { + setPositions(FbxMeshUtil.getDoubleArray(e)); + } else if (e.id.equals("PolygonVertexIndex")) { + setPolygonVertexIndices(FbxMeshUtil.getIntArray(e)); + } else if (e.id.equals("Edges")) { + setEdges(FbxMeshUtil.getIntArray(e)); + } else if (e.id.startsWith("LayerElement")) { + layerElementsList.add(FbxLayerElement.fromElement(e)); + } else if (e.id.equals("Layer")) { + layersList.add(FbxLayer.fromElement(e)); + } + } + + for (FbxLayer layer : layersList) { + layer.setLayerElements(layerElementsList); + } + + layerElements = new FbxLayerElement[layerElementsList.size()]; + layerElementsList.toArray(layerElements); + + layers = new FbxLayer[layersList.size()]; + layersList.toArray(layers); + } + + public FbxSkinDeformer getSkinDeformer() { + return skinDeformer; + } + + public void applyCluster(FbxCluster cluster) { + if (boneIndices == null) { + boneIndices = new ArrayList[positions.length]; + boneWeights = new ArrayList[positions.length]; + } + + FbxLimbNode limb = cluster.getLimb(); + Bone bone = limb.getJmeBone(); + Skeleton skeleton = limb.getSkeletonHolder().getJmeSkeleton(); + int boneIndex = skeleton.getBoneIndex(bone); + + int[] positionIndices = cluster.getVertexIndices(); + double[] weights = cluster.getWeights(); + + for (int i = 0; i < positionIndices.length; i++) { + int positionIndex = positionIndices[i]; + float boneWeight = (float)weights[i]; + + ArrayList boneIndicesForVertex = boneIndices[positionIndex]; + ArrayList boneWeightsForVertex = boneWeights[positionIndex]; + + if (boneIndicesForVertex == null) { + boneIndicesForVertex = new ArrayList(); + boneWeightsForVertex = new ArrayList(); + boneIndices[positionIndex] = boneIndicesForVertex; + boneWeights[positionIndex] = boneWeightsForVertex; + } + + boneIndicesForVertex.add(boneIndex); + boneWeightsForVertex.add(boneWeight); + } + } + + @Override + public void connectObject(FbxObject object) { + if (object instanceof FbxSkinDeformer) { + if (skinDeformer != null) { + logger.log(Level.WARNING, "This mesh already has a skin deformer attached. Ignoring."); + return; + } + skinDeformer = (FbxSkinDeformer) object; + } else { + unsupportedConnectObject(object); + } + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + + private void setPositions(double[] positions) { + this.positions = FbxLayerElement.toVector3(positions); + } + + private void setEdges(int[] edges) { + this.edges = edges; + // TODO: ... + } + + private void setPolygonVertexIndices(int[] polygonVertexIndices) { + List polygonList = new ArrayList(); + + boolean finishPolygon = false; + List vertexIndices = new ArrayList(); + + for (int i = 0; i < polygonVertexIndices.length; i++) { + int vertexIndex = polygonVertexIndices[i]; + + if (vertexIndex < 0) { + vertexIndex ^= -1; + finishPolygon = true; + } + + vertexIndices.add(vertexIndex); + + if (finishPolygon) { + finishPolygon = false; + polygonList.add(FbxPolygon.fromIndices(vertexIndices)); + vertexIndices.clear(); + } + } + + polygons = new FbxPolygon[polygonList.size()]; + polygonList.toArray(polygons); + } + + private static IrBoneWeightIndex[] toBoneWeightIndices(List boneIndices, List boneWeights) { + IrBoneWeightIndex[] boneWeightIndices = new IrBoneWeightIndex[boneIndices.size()]; + for (int i = 0; i < boneIndices.size(); i++) { + boneWeightIndices[i] = new IrBoneWeightIndex(boneIndices.get(i), boneWeights.get(i)); + } + return boneWeightIndices; + } + + @Override + protected IntMap toJmeObject() { + // Load clusters from SkinDeformer + if (skinDeformer != null) { + for (FbxCluster cluster : skinDeformer.getJmeObject()) { + applyCluster(cluster); + } + } + + IrMesh irMesh = toIRMesh(); + + // Trim bone weights to 4 weights per vertex. + IrUtils.trimBoneWeights(irMesh); + + // Convert tangents / binormals to tangents with parity. + IrUtils.toTangentsWithParity(irMesh); + + // Triangulate quads. + IrUtils.triangulate(irMesh); + + // Split meshes by material indices. + IntMap irMeshes = IrUtils.splitByMaterial(irMesh); + + // Create a jME3 Mesh for each material index. + IntMap jmeMeshes = new IntMap(); + for (IntMap.Entry irMeshEntry : irMeshes) { + Mesh jmeMesh = IrUtils.convertIrMeshToJmeMesh(irMeshEntry.getValue()); + jmeMeshes.put(irMeshEntry.getKey(), jmeMesh); + } + + if (jmeMeshes.size() == 0) { + // When will this actually happen? Not sure. + logger.log(Level.WARNING, "Empty FBX mesh found (unusual)."); + } + + // IMPORTANT: If we have a -1 entry, those are triangles + // with no material indices. + // It makes sense only if the mesh uses a single material! + if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) { + logger.log(Level.WARNING, "Mesh has polygons with no material " + + "indices (unusual) - they will use material index 0."); + } + + return jmeMeshes; + } + + /** + * Convert FBXMesh to IRMesh. + */ + public IrMesh toIRMesh() { + IrMesh newMesh = new IrMesh(); + newMesh.polygons = new IrPolygon[polygons.length]; + + int polygonVertexIndex = 0; + int positionIndex = 0; + + FbxLayer layer0 = layers[0]; + FbxLayer layer1 = layers.length > 1 ? layers[1] : null; + + for (int i = 0; i < polygons.length; i++) { + FbxPolygon polygon = polygons[i]; + IrPolygon irPolygon = new IrPolygon(); + irPolygon.vertices = new IrVertex[polygon.indices.length]; + + for (int j = 0; j < polygon.indices.length; j++) { + positionIndex = polygon.indices[j]; + + IrVertex irVertex = new IrVertex(); + irVertex.pos = positions[positionIndex]; + + if (layer0 != null) { + irVertex.norm = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Normal, i, polygonVertexIndex, positionIndex, 0); + irVertex.tang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Tangent, i, polygonVertexIndex, positionIndex, 0); + irVertex.bitang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Binormal, i, polygonVertexIndex, positionIndex, 0); + irVertex.uv0 = (Vector2f) layer0.getVertexData(FbxLayerElement.Type.UV, i, polygonVertexIndex, positionIndex, 0); + irVertex.color = (ColorRGBA) layer0.getVertexData(FbxLayerElement.Type.Color, i, polygonVertexIndex, positionIndex, 0); + irVertex.material = (Integer) layer0.getVertexData(FbxLayerElement.Type.Material, i, polygonVertexIndex, positionIndex, 0); + irVertex.smoothing = (Integer) layer0.getVertexData(FbxLayerElement.Type.Smoothing, i, polygonVertexIndex, positionIndex, 0); + } + + if (layer1 != null) { + irVertex.uv1 = (Vector2f) layer1.getVertexData(FbxLayerElement.Type.UV, i, + polygonVertexIndex, positionIndex, 0); + } + + if (boneIndices != null) { + ArrayList boneIndicesForVertex = boneIndices[positionIndex]; + ArrayList boneWeightsForVertex = boneWeights[positionIndex]; + if (boneIndicesForVertex != null) { + irVertex.boneWeightsIndices = toBoneWeightIndices(boneIndicesForVertex, boneWeightsForVertex); + } + } + + irPolygon.vertices[j] = irVertex; + + polygonVertexIndex++; + } + + newMesh.polygons[i] = irPolygon; + } + + // Ensure "inspection vertex" specifies that mesh has bone indices / weights + if (boneIndices != null && newMesh.polygons[0].vertices[0] == null) { + newMesh.polygons[0].vertices[0].boneWeightsIndices = new IrBoneWeightIndex[0]; + } + + return newMesh; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java new file mode 100644 index 000000000..61fb001dd --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.mesh; + +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxMeshUtil { + + public static double[] getDoubleArray(FbxElement el) { + if (el.propertiesTypes[0] == 'd') { + // FBX 7.x + return (double[]) el.properties.get(0); + } else if (el.propertiesTypes[0] == 'D') { + // FBX 6.x + double[] doubles = new double[el.propertiesTypes.length]; + for (int i = 0; i < doubles.length; i++) { + doubles[i] = (Double) el.properties.get(i); + } + return doubles; + } else { + return null; + } + } + + public static int[] getIntArray(FbxElement el) { + if (el.propertiesTypes[0] == 'i') { + // FBX 7.x + return (int[]) el.properties.get(0); + } else if (el.propertiesTypes[0] == 'I') { + // FBX 6.x + int[] ints = new int[el.propertiesTypes.length]; + for (int i = 0; i < ints.length; i++) { + ints[i] = (Integer) el.properties.get(i); + } + return ints; + } else { + return null; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java similarity index 67% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java index eeeee872c..bb7773785 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2015 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,36 +29,31 @@ * NEGLIGENCE OR 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.fbx.file; +package com.jme3.scene.plugins.fbx.mesh; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -public class FBXElement { - - public String id; - public List properties; - /* - * Y - signed short - * C - boolean - * I - signed integer - * F - float - * D - double - * L - long - * R - byte array - * S - string - * f - array of floats - * i - array of ints - * d - array of doubles - * l - array of longs - * b - array of boleans - * c - array of unsigned bytes (represented as array of ints) - */ - public char[] propertiesTypes; - public List children = new ArrayList(); - - public FBXElement(int propsCount) { - properties = new ArrayList(propsCount); - propertiesTypes = new char[propsCount]; - } +public final class FbxPolygon { + + int[] indices; + + @Override + public String toString() { + return Arrays.toString(indices); + } + + private static int[] listToArray(List indices) { + int[] indicesArray = new int[indices.size()]; + for (int i = 0; i < indices.size(); i++) { + indicesArray[i] = indices.get(i); + } + return indicesArray; + } + + public static FbxPolygon fromIndices(List indices) { + FbxPolygon poly = new FbxPolygon(); + poly.indices = listToArray(indices); + return poly; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java new file mode 100644 index 000000000..3a815df6a --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.misc; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxGlobalSettings { + + private static final Logger logger = Logger.getLogger(FbxGlobalSettings.class.getName()); + + private static final Map timeModeToFps = new HashMap(); + + static { + timeModeToFps.put(1, 120f); + timeModeToFps.put(2, 100f); + timeModeToFps.put(3, 60f); + timeModeToFps.put(4, 50f); + timeModeToFps.put(5, 48f); + timeModeToFps.put(6, 30f); + timeModeToFps.put(9, 30f / 1.001f); + timeModeToFps.put(10, 25f); + timeModeToFps.put(11, 24f); + timeModeToFps.put(13, 24f / 1.001f); + timeModeToFps.put(14, -1f); + timeModeToFps.put(15, 96f); + timeModeToFps.put(16, 72f); + timeModeToFps.put(17, 60f / 1.001f); + } + + public float unitScaleFactor = 1.0f; + public ColorRGBA ambientColor = ColorRGBA.Black; + public float frameRate = 25.0f; + + /** + * @return A {@link Transform} that converts from the FBX file coordinate + * system to jME3 coordinate system. + * jME3's coordinate system is: + *
    + *
  • Units are specified in meters.
  • + *
  • Orientation is right-handed with Y-up.
  • + *
+ */ + public Transform getGlobalTransform() { + // Default unit scale factor is 1 (centimeters), + // convert to meters. + float scale = unitScaleFactor / 100.0f; + + // TODO: handle rotation + + return new Transform(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(scale, scale, scale)); + } + + public void fromElement(FbxElement element) { + // jME3 uses a +Y up, -Z forward coordinate system (same as OpenGL) + // Luckily enough, this is also the default for FBX models. + + int timeMode = -1; + float customFrameRate = 30.0f; + + for (FbxElement e2 : element.getFbxProperties()) { + String propName = (String) e2.properties.get(0); + if (propName.equals("UnitScaleFactor")) { + unitScaleFactor = ((Double) e2.properties.get(4)).floatValue(); + if (unitScaleFactor != 100.0f) { + logger.log(Level.WARNING, "FBX model isn't using meters for world units. Scale could be incorrect."); + } + } else if (propName.equals("TimeMode")) { + timeMode = (Integer) e2.properties.get(4); + } else if (propName.equals("CustomFrameRate")) { + float framerate = ((Double) e2.properties.get(4)).floatValue(); + if (framerate != -1) { + customFrameRate = framerate; + } + } else if (propName.equals("UpAxis")) { + Integer upAxis = (Integer) e2.properties.get(4); + if (upAxis != 1) { + logger.log(Level.WARNING, "FBX model isn't using Y as up axis. Orientation could be incorrect"); + } + } else if (propName.equals("UpAxisSign")) { + Integer upAxisSign = (Integer) e2.properties.get(4); + if (upAxisSign != 1) { + logger.log(Level.WARNING, "FBX model isn't using correct up axis sign. Orientation could be incorrect"); + } + } else if (propName.equals("FrontAxis")) { + Integer frontAxis = (Integer) e2.properties.get(4); + if (frontAxis != 2) { + logger.log(Level.WARNING, "FBX model isn't using Z as forward axis. Orientation could be incorrect"); + } + } else if (propName.equals("FrontAxisSign")) { + Integer frontAxisSign = (Integer) e2.properties.get(4); + if (frontAxisSign != -1) { + logger.log(Level.WARNING, "FBX model isn't using correct forward axis sign. Orientation could be incorrect"); + } + } + } + + Float fps = timeModeToFps.get(timeMode); + if (fps != null) { + if (fps == -1f) { + // Using custom framerate + frameRate = customFrameRate; + } else { + // Use FPS from time mode. + frameRate = fps; + } + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java new file mode 100644 index 000000000..c0ce06d4b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.node; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.debug.SkeletonDebugger; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; +import com.jme3.scene.plugins.fbx.anim.FbxCluster; +import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; +import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.material.FbxImage; +import com.jme3.scene.plugins.fbx.material.FbxMaterial; +import com.jme3.scene.plugins.fbx.material.FbxTexture; +import com.jme3.scene.plugins.fbx.mesh.FbxMesh; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import com.jme3.util.IntMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxNode extends FbxObject { + + private static final Logger logger = Logger.getLogger(FbxNode.class.getName()); + + private static enum InheritMode { + /** + * Apply parent scale after child rotation. + * This is the only mode correctly supported by jME3. + */ + ScaleAfterChildRotation, + + /** + * Apply parent scale before child rotation. + * Not supported by jME3, will cause distortion with + * non-uniform scale. No way around it. + */ + ScaleBeforeChildRotation, + + /** + * Do not apply parent scale at all. + * Not supported by jME3, will cause distortion. + * Could be worked around by via: + * jmeChildScale = jmeParentScale / fbxChildScale + */ + NoParentScale + } + + private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation; + + protected FbxNode parent; + protected List children = new ArrayList(); + protected List materials = new ArrayList(); + protected Map userData = new HashMap(); + protected Map> propertyToAnimCurveMap = new HashMap>(); + protected FbxNodeAttribute nodeAttribute; + protected double visibility = 1.0; + + /** + * For FBX nodes that contain a skeleton (i.e. FBX limbs). + */ + protected Skeleton skeleton; + + protected final Transform jmeWorldNodeTransform = new Transform(); + protected final Transform jmeLocalNodeTransform = new Transform(); + + // optional - used for limbs / bones / skeletons + protected Transform jmeWorldBindPose; + protected Transform jmeLocalBindPose; + + // used for debugging only + protected Matrix4f cachedWorldBindPose; + + public FbxNode(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + public Transform computeFbxLocalTransform() { + // TODO: implement the actual algorithm, which is this: + // Render Local Translation = + // Inv Scale Pivot * Lcl Scale * Scale Pivot * Scale Offset * Inv Rota Pivot * Post Rotation * Rotation * Pre Rotation * Rotation Pivot * Rotation Offset * Translation + + // LclTranslation, + // LclRotation, + // PreRotation, + // PostRotation, + // RotationPivot, + // RotationOffset, + // LclScaling, + // ScalingPivot, + // ScalingOffset + + Matrix4f scaleMat = new Matrix4f(); + scaleMat.setScale(jmeLocalNodeTransform.getScale()); + + Matrix4f rotationMat = new Matrix4f(); + rotationMat.setRotationQuaternion(jmeLocalNodeTransform.getRotation()); + + Matrix4f translationMat = new Matrix4f(); + translationMat.setTranslation(jmeLocalNodeTransform.getTranslation()); + + Matrix4f result = new Matrix4f(); + result.multLocal(scaleMat).multLocal(rotationMat).multLocal(translationMat); + + Transform t = new Transform(); + t.fromTransformMatrix(result); + + return t; + } + + public void setWorldBindPose(Matrix4f worldBindPose) { + if (cachedWorldBindPose != null) { + if (!cachedWorldBindPose.equals(worldBindPose)) { + throw new UnsupportedOperationException("Bind poses don't match"); + } + } + + cachedWorldBindPose = worldBindPose; + + this.jmeWorldBindPose = new Transform(); + this.jmeWorldBindPose.setTranslation(worldBindPose.toTranslationVector()); + this.jmeWorldBindPose.setRotation(worldBindPose.toRotationQuat()); + this.jmeWorldBindPose.setScale(worldBindPose.toScaleVector()); + + System.out.println("\tBind Pose for " + getName()); + System.out.println(jmeWorldBindPose); + + float[] angles = new float[3]; + jmeWorldBindPose.getRotation().toAngles(angles); + System.out.println("Angles: " + angles[0] * FastMath.RAD_TO_DEG + ", " + + angles[1] * FastMath.RAD_TO_DEG + ", " + + angles[2] * FastMath.RAD_TO_DEG); + } + + public void updateWorldTransforms(Transform jmeParentNodeTransform, Transform parentBindPose) { + Transform fbxLocalTransform = computeFbxLocalTransform(); + jmeLocalNodeTransform.set(fbxLocalTransform); + + if (jmeParentNodeTransform != null) { + jmeParentNodeTransform = jmeParentNodeTransform.clone(); + switch (inheritMode) { + case NoParentScale: + case ScaleAfterChildRotation: + case ScaleBeforeChildRotation: + jmeWorldNodeTransform.set(jmeLocalNodeTransform); + jmeWorldNodeTransform.combineWithParent(jmeParentNodeTransform); + break; + } + } else { + jmeWorldNodeTransform.set(jmeLocalNodeTransform); + } + + if (jmeWorldBindPose != null) { + jmeLocalBindPose = new Transform(); + + // Need to derive local bind pose from world bind pose + // (this is to be expected for FBX limbs) + jmeLocalBindPose.set(jmeWorldBindPose); + jmeLocalBindPose.combineWithParent(parentBindPose.invert()); + + // Its somewhat odd for the transforms to differ ... + System.out.println("Bind Pose for: " + getName()); + if (!jmeLocalBindPose.equals(jmeLocalNodeTransform)) { + System.out.println("Local Bind: " + jmeLocalBindPose); + System.out.println("Local Trans: " + jmeLocalNodeTransform); + } + if (!jmeWorldBindPose.equals(jmeWorldNodeTransform)) { + System.out.println("World Bind: " + jmeWorldBindPose); + System.out.println("World Trans: " + jmeWorldNodeTransform); + } + } else { + // World pose derived from local transforms + // (this is to be expected for FBX nodes) + jmeLocalBindPose = new Transform(); + jmeWorldBindPose = new Transform(); + + jmeLocalBindPose.set(jmeLocalNodeTransform); + if (parentBindPose != null) { + jmeWorldBindPose.set(jmeLocalNodeTransform); + jmeWorldBindPose.combineWithParent(parentBindPose); + } else { + jmeWorldBindPose.set(jmeWorldNodeTransform); + } + } + + for (FbxNode child : children) { + child.updateWorldTransforms(jmeWorldNodeTransform, jmeWorldBindPose); + } + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + + Vector3f localTranslation = new Vector3f(); + Quaternion localRotation = new Quaternion(); + Vector3f localScale = new Vector3f(Vector3f.UNIT_XYZ); + Quaternion preRotation = new Quaternion(); + + for (FbxElement e2 : element.getFbxProperties()) { + String propName = (String) e2.properties.get(0); + String type = (String) e2.properties.get(3); + if (propName.equals("Lcl Translation")) { + double x = (Double) e2.properties.get(4); + double y = (Double) e2.properties.get(5); + double z = (Double) e2.properties.get(6); + localTranslation.set((float) x, (float) y, (float) z); //.divideLocal(unitSize); + } else if (propName.equals("Lcl Rotation")) { + double x = (Double) e2.properties.get(4); + double y = (Double) e2.properties.get(5); + double z = (Double) e2.properties.get(6); + localRotation.fromAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD); + } else if (propName.equals("Lcl Scaling")) { + double x = (Double) e2.properties.get(4); + double y = (Double) e2.properties.get(5); + double z = (Double) e2.properties.get(6); + localScale.set((float) x, (float) y, (float) z); //.multLocal(unitSize); + } else if (propName.equals("PreRotation")) { + double x = (Double) e2.properties.get(4); + double y = (Double) e2.properties.get(5); + double z = (Double) e2.properties.get(6); + preRotation.set(FbxNodeUtil.quatFromBoneAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD)); + } else if (propName.equals("InheritType")) { + int inheritType = (Integer) e2.properties.get(4); + inheritMode = InheritMode.values()[inheritType]; + } else if (propName.equals("Visibility")) { + visibility = (Double) e2.properties.get(4); + } else if (type.contains("U")) { + String userDataKey = (String) e2.properties.get(0); + String userDataType = (String) e2.properties.get(1); + Object userDataValue; + + if (userDataType.equals("KString")) { + userDataValue = (String) e2.properties.get(4); + } else if (userDataType.equals("int")) { + userDataValue = (Integer) e2.properties.get(4); + } else if (userDataType.equals("double")) { + // NOTE: jME3 does not support doubles in UserData. + // Need to convert to float. + userDataValue = ((Double) e2.properties.get(4)).floatValue(); + } else if (userDataType.equals("Vector")) { + float x = ((Double) e2.properties.get(4)).floatValue(); + float y = ((Double) e2.properties.get(5)).floatValue(); + float z = ((Double) e2.properties.get(6)).floatValue(); + userDataValue = new Vector3f(x, y, z); + } else { + logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType); + continue; + } + + userData.put(userDataKey, userDataValue); + } + } + + // Create local transform + // TODO: take into account Maya-style transforms (pre / post rotation ..) + jmeLocalNodeTransform.setTranslation(localTranslation); + jmeLocalNodeTransform.setRotation(localRotation); + jmeLocalNodeTransform.setScale(localScale); + + if (element.getChildById("Vertices") != null) { + // This is an old-style FBX 6.1 + // Meshes could be embedded inside the node.. + + // Inject the mesh into ourselves.. + FbxMesh mesh = new FbxMesh(assetManager, sceneFolderName); + mesh.fromElement(element); + connectObject(mesh); + } + } + + private Spatial tryCreateGeometry(int materialIndex, Mesh jmeMesh, boolean single) { + // Map meshes without material indices to material 0. + if (materialIndex == -1) { + materialIndex = 0; + } + + Material jmeMat; + if (materialIndex >= materials.size()) { + // Material index does not exist. Create default material. + jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + jmeMat.setReceivesShadows(true); + } else { + FbxMaterial fbxMat = materials.get(materialIndex); + jmeMat = fbxMat.getJmeObject(); + } + + String geomName = getName(); + if (single) { + geomName += "-submesh"; + } else { + geomName += "-mat-" + materialIndex + "-submesh"; + } + Spatial spatial = new Geometry(geomName, jmeMesh); + spatial.setMaterial(jmeMat); + if (jmeMat.isTransparent()) { + spatial.setQueueBucket(Bucket.Transparent); + } + if (jmeMat.isReceivesShadows()) { + spatial.setShadowMode(ShadowMode.Receive); + } + spatial.updateModelBound(); + return spatial; + } + + /** + * If this geometry node is deformed by a skeleton, this + * returns the node containing the skeleton. + * + * In jME3, a mesh can be deformed by a skeleton only if it is + * a child of the node containing the skeleton. However, this + * is not a requirement in FBX, so we have to modify the scene graph + * of the loaded model to adjust for this. + * This happens automatically in + * {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}. + * + * @return The model this node would like to be a child of, or null + * if no preferred parent. + */ + public FbxNode getPreferredParent() { + if (!(nodeAttribute instanceof FbxMesh)) { + return null; + } + + FbxMesh fbxMesh = (FbxMesh) nodeAttribute; + FbxSkinDeformer deformer = fbxMesh.getSkinDeformer(); + FbxNode preferredParent = null; + + if (deformer != null) { + for (FbxCluster cluster : deformer.getJmeObject()) { + FbxLimbNode limb = cluster.getLimb(); + if (preferredParent == null) { + preferredParent = limb.getSkeletonHolder(); + } else if (preferredParent != limb.getSkeletonHolder()) { + logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. " + + "Only one skeleton will work, ignoring other skeletons."); + } + } + } + + return preferredParent; + } + + @Override + public Spatial toJmeObject() { + Spatial spatial; + + if (nodeAttribute instanceof FbxMesh) { + FbxMesh fbxMesh = (FbxMesh) nodeAttribute; + IntMap jmeMeshes = fbxMesh.getJmeObject(); + + if (jmeMeshes == null || jmeMeshes.size() == 0) { + // No meshes found on FBXMesh (??) + logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node."); + spatial = new Node(getName() + "-node"); + } else { + // Multiple jME3 geometries required for a single FBXMesh. + String nodeName; + if (children.isEmpty()) { + nodeName = getName() + "-mesh"; + } else { + nodeName = getName() + "-node"; + } + Node node = new Node(nodeName); + boolean singleMesh = jmeMeshes.size() == 1; + for (IntMap.Entry meshInfo : jmeMeshes) { + node.attachChild(tryCreateGeometry(meshInfo.getKey(), meshInfo.getValue(), singleMesh)); + } + spatial = node; + } + } else { + if (nodeAttribute != null) { + // Just specifies that this is a "null" node. + nodeAttribute.getJmeObject(); + } + + // TODO: handle other node attribute types. + // right now everything we don't know about gets converted + // to jME3 Node. + spatial = new Node(getName() + "-node"); + } + + if (!children.isEmpty()) { + // Check uniform scale. + // Although, if inheritType is 0 (eInheritRrSs) + // it might not be a problem. + Vector3f localScale = jmeLocalNodeTransform.getScale(); + if (!FastMath.approximateEquals(localScale.x, localScale.y) || + !FastMath.approximateEquals(localScale.x, localScale.z)) { + logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " + + "The model may appear distorted."); + } + } + + spatial.setLocalTransform(jmeLocalNodeTransform); + + if (visibility == 0.0) { + spatial.setCullHint(CullHint.Always); + } + + for (Map.Entry userDataEntry : userData.entrySet()) { + spatial.setUserData(userDataEntry.getKey(), userDataEntry.getValue()); + } + + return spatial; + } + + /** + * Create jME3 Skeleton objects on the scene. + * + * Goes through the scene graph and finds limbs that are + * attached to FBX nodes, then creates a Skeleton on the node + * based on the child limbs. + * + * Must be called prior to calling + * {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}. + * + * @param fbxNode The root FBX node. + */ + public static void createSkeletons(FbxNode fbxNode) { + boolean createSkeleton = false; + for (FbxNode fbxChild : fbxNode.children) { + if (fbxChild instanceof FbxLimbNode) { + createSkeleton = true; + } else { + createSkeletons(fbxChild); + } + } + if (createSkeleton) { + if (fbxNode.skeleton != null) { + throw new UnsupportedOperationException(); + } + fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode); + System.out.println("created skeleton: " + fbxNode.skeleton); + } + } + + private static void relocateSpatial(Spatial spatial, + Transform originalWorldTransform, Transform newWorldTransform) { + Transform localTransform = new Transform(); + localTransform.set(originalWorldTransform); + localTransform.combineWithParent(newWorldTransform.invert()); + spatial.setLocalTransform(localTransform); + } + + public static Spatial createScene(FbxNode fbxNode) { + Spatial jmeSpatial = fbxNode.getJmeObject(); + + if (jmeSpatial instanceof Node) { + // Attach children to Node + Node jmeNode = (Node) jmeSpatial; + for (FbxNode fbxChild : fbxNode.children) { + if (!(fbxChild instanceof FbxLimbNode)) { + createScene(fbxChild); + + FbxNode preferredParent = fbxChild.getPreferredParent(); + Spatial jmeChild = fbxChild.getJmeObject(); + if (preferredParent != null) { + System.out.println("Preferred parent for " + fbxChild + " is " + preferredParent); + + Node jmePreferredParent = (Node) preferredParent.getJmeObject(); + relocateSpatial(jmeChild, fbxChild.jmeWorldNodeTransform, + preferredParent.jmeWorldNodeTransform); + jmePreferredParent.attachChild(jmeChild); + } else { + jmeNode.attachChild(jmeChild); + } + } + } + } + + if (fbxNode.skeleton != null) { + jmeSpatial.addControl(new AnimControl(fbxNode.skeleton)); + jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton)); + + SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton); + Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setDepthTest(false); + mat.setColor("Color", ColorRGBA.Green); + sd.setMaterial(mat); + + ((Node)jmeSpatial).attachChild(sd); + } + + return jmeSpatial; + } + +// public SceneLoader.Limb toLimb() { +// SceneLoader.Limb limb = new SceneLoader.Limb(); +// limb.name = getName(); +// Quaternion rotation = preRotation.mult(localRotation); +// limb.bindTransform = new Transform(localTranslation, rotation, localScale); +// return limb; +// } + + public Skeleton getJmeSkeleton() { + return skeleton; + } + + public List getChildren() { + return children; + } + + @Override + public void connectObject(FbxObject object) { + if (object instanceof FbxNode) { + // Scene Graph Object + FbxNode childNode = (FbxNode) object; + if (childNode.parent != null) { + throw new IllegalStateException("Cannot attach " + childNode + + " to " + this + ". It is already " + + "attached to " + childNode.parent); + } + childNode.parent = this; + children.add(childNode); + } else if (object instanceof FbxNodeAttribute) { + // Node Attribute + if (nodeAttribute != null) { + throw new IllegalStateException("An FBXNodeAttribute (" + nodeAttribute + ")" + + " is already attached to " + this + ". " + + "Only one attribute allowed per node."); + } + + nodeAttribute = (FbxNodeAttribute) object; + if (nodeAttribute instanceof FbxNullAttribute) { + nodeAttribute.getJmeObject(); + } + } else if (object instanceof FbxMaterial) { + materials.add((FbxMaterial) object); + } else if (object instanceof FbxImage || object instanceof FbxTexture) { + // Ignore - attaching textures to nodes is legacy feature. + } else { + unsupportedConnectObject(object); + } + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + // Only allowed to connect local transform properties to object + // (FbxAnimCurveNode) + if (object instanceof FbxAnimCurveNode) { + FbxAnimCurveNode curveNode = (FbxAnimCurveNode) object; + if (property.equals("Lcl Translation") + || property.equals("Lcl Rotation") + || property.equals("Lcl Scaling")) { + + List curveNodes = propertyToAnimCurveMap.get(property); + if (curveNodes == null) { + curveNodes = new ArrayList(); + curveNodes.add(curveNode); + propertyToAnimCurveMap.put(property, curveNodes); + } + curveNodes.add(curveNode); + + // Make sure the curve knows about it animating + // this node as well. + curveNode.addInfluencedNode(this, property); + } else { + logger.log(Level.WARNING, "Animating the property ''{0}'' is not " + + "supported. Ignoring.", property); + } + } else { + unsupportedConnectObjectProperty(object, property); + } + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java new file mode 100644 index 000000000..b63e4bc08 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.node; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.obj.FbxObject; + +public abstract class FbxNodeAttribute extends FbxObject { + public FbxNodeAttribute(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java new file mode 100644 index 000000000..8c35e45e9 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.node; + +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; + +public class FbxNodeUtil { + public static Quaternion quatFromBoneAngles(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); + float cosYXcosZ = cosY * cosZ; + float sinYXsinZ = sinY * sinZ; + float cosYXsinZ = cosY * sinZ; + float sinYXcosZ = sinY * cosZ; + // For some reason bone space is differ, this is modified formulas + float w = (cosYXcosZ * cosX + sinYXsinZ * sinX); + float x = (cosYXcosZ * sinX - sinYXsinZ * cosX); + float y = (sinYXcosZ * cosX + cosYXsinZ * sinX); + float z = (cosYXsinZ * cosX - sinYXcosZ * sinX); + return new Quaternion(x, y, z, w).normalizeLocal(); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java new file mode 100644 index 000000000..ce95546d8 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.node; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.obj.FbxObject; + +public class FbxNullAttribute extends FbxNodeAttribute { + + public FbxNullAttribute(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected Object toJmeObject() { + // No data in a "Null" attribute. + return new Object(); + } + + @Override + public void connectObject(FbxObject object) { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + throw new UnsupportedOperationException(); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java new file mode 100644 index 000000000..83db50283 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.node; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.file.FbxId; + +public class FbxRootNode extends FbxNode { + public FbxRootNode(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + this.id = FbxId.ROOT; + this.className = "Model"; + this.name = "Scene"; + this.subclassName = ""; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java new file mode 100644 index 000000000..d9439a2d4 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.obj; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.file.FbxId; +import java.util.logging.Logger; + +public abstract class FbxObject { + + private static final Logger logger = Logger.getLogger(FbxObject.class.getName()); + + protected AssetManager assetManager; + protected String sceneFolderName; + + protected FbxId id; + protected String name; + protected String className; + protected String subclassName; + + protected JT jmeObject; // lazily initialized + + protected FbxObject(AssetManager assetManager, String sceneFolderName) { + this.assetManager = assetManager; + this.sceneFolderName = sceneFolderName; + } + + public FbxId getId() { + return id; + } + + public String getName() { + return name; + } + + public String getClassName() { + return className; + } + + public String getSubclassName() { + return subclassName; + } + + public String getFullClassName() { + if (subclassName.equals("")) { + return className; + } else { + return subclassName + " : " + className; + } + } + + @Override + public String toString() { + return name + " (" + id + ")"; + } + + protected void fromElement(FbxElement element) { + id = FbxId.getObjectId(element); + String nameAndClass; + if (element.propertiesTypes.length == 3) { + nameAndClass = (String) element.properties.get(1); + subclassName = (String) element.properties.get(2); + } else if (element.propertiesTypes.length == 2) { + nameAndClass = (String) element.properties.get(0); + subclassName = (String) element.properties.get(1); + } else { + throw new UnsupportedOperationException("This is not an FBX object: " + element.id); + } + + int splitter = nameAndClass.indexOf("\u0000\u0001"); + + if (splitter != -1) { + name = nameAndClass.substring(0, splitter); + className = nameAndClass.substring(splitter + 2); + } else { + name = nameAndClass; + className = null; + } + } + + public final JT getJmeObject() { + if (jmeObject == null) { + jmeObject = toJmeObject(); + if (jmeObject == null) { + throw new UnsupportedOperationException("FBX object subclass " + + "failed to resolve to a jME3 object"); + } + } + return jmeObject; + } + + public final boolean isJmeObjectCreated() { + return jmeObject != null; + } + + protected final void unsupportedConnectObject(FbxObject object) { + throw new IllegalArgumentException("Cannot attach objects of this class (" + + object.getFullClassName() + + ") to " + getClass().getSimpleName()); + } + + protected final void unsupportedConnectObjectProperty(FbxObject object, String property) { + throw new IllegalArgumentException("Cannot attach objects of this class (" + + object.getFullClassName() + + ") to property " + getClass().getSimpleName() + + "[\"" + property + "\"]"); + } + + protected abstract JT toJmeObject(); + + public abstract void connectObject(FbxObject object); + + public abstract void connectObjectProperty(FbxObject object, String property); +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java new file mode 100644 index 000000000..ec8c1fd67 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.obj; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurve; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; +import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer; +import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; +import com.jme3.scene.plugins.fbx.anim.FbxBindPose; +import com.jme3.scene.plugins.fbx.anim.FbxCluster; +import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; +import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.material.FbxImage; +import com.jme3.scene.plugins.fbx.material.FbxMaterial; +import com.jme3.scene.plugins.fbx.material.FbxTexture; +import com.jme3.scene.plugins.fbx.mesh.FbxMesh; +import com.jme3.scene.plugins.fbx.node.FbxNode; +import com.jme3.scene.plugins.fbx.node.FbxNullAttribute; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Responsible for producing FBX objects given an FBXElement. + */ +public final class FbxObjectFactory { + + private static final Logger logger = Logger.getLogger(FbxObjectFactory.class.getName()); + + private static Class getImplementingClass(String elementName, String subclassName) { + if (elementName.equals("NodeAttribute")) { + if (subclassName.equals("Root")) { + // Root of skeleton, may not actually be set. + return FbxNullAttribute.class; + } else if (subclassName.equals("LimbNode")) { + // Specifies some limb attributes, optional. + return FbxNullAttribute.class; + } else if (subclassName.equals("Null")) { + // An "Empty" or "Node" without any specific behavior. + return FbxNullAttribute.class; + } else if (subclassName.equals("IKEffector") || + subclassName.equals("FKEffector")) { + // jME3 does not support IK. + return FbxNullAttribute.class; + } else { + // NodeAttribute - Unknown + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Geometry") && subclassName.equals("Mesh")) { + // NodeAttribute - Mesh Data + return FbxMesh.class; + } else if (elementName.equals("Model")) { + // Scene Graph Node + // Determine specific subclass (e.g. Mesh, Null, or LimbNode?) + if (subclassName.equals("LimbNode")) { + return FbxLimbNode.class; // Child Bone of Skeleton? + } else { + return FbxNode.class; + } + } else if (elementName.equals("Pose")) { + if (subclassName.equals("BindPose")) { + // Bind Pose Information + return FbxBindPose.class; + } else { + // Rest Pose Information + // OR + // Other Data (???) + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Material")) { + return FbxMaterial.class; + } else if (elementName.equals("Deformer")) { + // Deformer + if (subclassName.equals("Skin")) { + // FBXSkinDeformer (mapping between FBXMesh & FBXClusters) + return FbxSkinDeformer.class; + } else if (subclassName.equals("Cluster")) { + // Cluster (aka mapping between FBXMesh vertices & weights for bone) + return FbxCluster.class; + } else { + logger.log(Level.WARNING, "Unknown deformer subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Video")) { + if (subclassName.equals("Clip")) { + return FbxImage.class; + } else { + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Texture")) { + return FbxTexture.class; + } else if (elementName.equals("AnimationStack")) { + // AnimationStack (jME Animation) + return FbxAnimStack.class; + } else if (elementName.equals("AnimationLayer")) { + // AnimationLayer (for blended animation - not supported) + return FbxAnimLayer.class; + } else if (elementName.equals("AnimationCurveNode")) { + // AnimationCurveNode + return FbxAnimCurveNode.class; + } else if (elementName.equals("AnimationCurve")) { + // AnimationCurve (Data) + return FbxAnimCurve.class; + } else if (elementName.equals("SceneInfo")) { + // Old-style FBX 6.1 uses this. Nothing useful here. + return FbxUnknownObject.class; + } else { + logger.log(Level.WARNING, "Unknown object class: {0}. Ignoring.", elementName); + return FbxUnknownObject.class; + } + } + + /** + * Automatically create an FBXObject by inspecting its class / subclass + * properties. + * + * @param element The element from which to create an object. + * @param assetManager AssetManager to load dependent resources + * @param sceneFolderName Folder relative to which resources shall be loaded + * @return The object, or null if not supported (?) + */ + public static FbxObject createObject(FbxElement element, AssetManager assetManager, String sceneFolderName) { + String elementName = element.id; + String subclassName; + + if (element.propertiesTypes.length == 3) { + // FBX 7.x (all objects start with Long ID) + subclassName = (String) element.properties.get(2); + } else if (element.propertiesTypes.length == 2) { + // FBX 6.x (objects only have name and subclass) + subclassName = (String) element.properties.get(1); + } else { + // Not an object or invalid data. + return null; + } + + Class javaFbxClass = getImplementingClass(elementName, subclassName); + + if (javaFbxClass != null) { + try { + // This object is supported by FBX importer, create new instance. + // Import the data into the object from the element, then return it. + Constructor ctor = javaFbxClass.getConstructor(AssetManager.class, String.class); + FbxObject obj = ctor.newInstance(assetManager, sceneFolderName); + obj.fromElement(element); + + String subClassName = elementName + ", " + subclassName; + if (obj.assetManager == null) { + throw new IllegalStateException("FBXObject subclass (" + subClassName + + ") forgot to call super() in their constructor"); + } else if (obj.className == null) { + throw new IllegalStateException("FBXObject subclass (" + subClassName + + ") forgot to call super.fromElement() in their fromElement() implementation"); + } + return obj; + } catch (InvocationTargetException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } catch (NoSuchMethodException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } catch (InstantiationException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } catch (IllegalAccessException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } + } + + // Not supported object. + return null; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java new file mode 100644 index 000000000..9a1eaa910 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.fbx.obj; + +import com.jme3.asset.AssetManager; + +public class FbxUnknownObject extends FbxObject { + + public FbxUnknownObject(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected Void toJmeObject() { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObject(FbxObject object) { + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java new file mode 100644 index 000000000..523993f51 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +public class IrBoneWeightIndex implements Cloneable, Comparable { + + int boneIndex; + float boneWeight; + + public IrBoneWeightIndex(int boneIndex, float boneWeight) { + this.boneIndex = boneIndex; + this.boneWeight = boneWeight; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(ex); + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + this.boneIndex; + hash = 23 * hash + Float.floatToIntBits(this.boneWeight); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IrBoneWeightIndex other = (IrBoneWeightIndex) obj; + if (this.boneIndex != other.boneIndex) { + return false; + } + if (Float.floatToIntBits(this.boneWeight) != Float.floatToIntBits(other.boneWeight)) { + return false; + } + return true; + } + + @Override + public int compareTo(IrBoneWeightIndex o) { + if (boneWeight < o.boneWeight) { + return 1; + } else if (boneWeight > o.boneWeight) { + return -1; + } else { + return 0; + } + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java new file mode 100644 index 000000000..8bb5a6881 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +public class IrMesh { + + public IrPolygon[] polygons; + + public IrMesh deepClone() { + IrMesh m = new IrMesh(); + m.polygons = new IrPolygon[polygons.length]; + for (int i = 0; i < polygons.length; i++) { + m.polygons[i] = polygons[i].deepClone(); + } + return m; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java new file mode 100644 index 000000000..7bb47cb54 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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; + +public class IrPolygon { + + public IrVertex[] vertices; + + public IrPolygon deepClone() { + IrPolygon p = new IrPolygon(); + p.vertices = new IrVertex[vertices.length]; + for (int i = 0; i < vertices.length; i++) { + p.vertices[i] = vertices[i].deepClone(); + } + return p; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java new file mode 100644 index 000000000..7b20e1670 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.math.Vector4f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +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.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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class IrUtils { + + private static final Logger logger = Logger.getLogger(IrUtils.class.getName()); + + private IrUtils() { } + + private static IrPolygon[] quadToTri(IrPolygon quad) { + if (quad.vertices.length == 3) { + throw new IllegalStateException("Already a triangle"); + } + + IrPolygon[] t = new IrPolygon[]{ new IrPolygon(), new IrPolygon() }; + t[0].vertices = new IrVertex[3]; + t[1].vertices = new IrVertex[3]; + + IrVertex v0 = quad.vertices[0]; + IrVertex v1 = quad.vertices[1]; + IrVertex v2 = quad.vertices[2]; + IrVertex v3 = quad.vertices[3]; + + // find the pair of verticies that is closest to each over + // v0 and v2 + // OR + // v1 and v3 + float d1 = v0.pos.distanceSquared(v2.pos); + float d2 = v1.pos.distanceSquared(v3.pos); + if (d1 < d2) { + // v0 is close to v2 + // put an edge in v0, v2 + t[0].vertices[0] = v0; + t[0].vertices[1] = v1; + t[0].vertices[2] = v3; + + t[1].vertices[0] = v1; + t[1].vertices[1] = v2; + t[1].vertices[2] = v3; + } else { + // put an edge in v1, v3 + t[0].vertices[0] = v0; + t[0].vertices[1] = v1; + t[0].vertices[2] = v2; + + t[1].vertices[0] = v0; + t[1].vertices[1] = v2; + t[1].vertices[2] = v3; + } + + return t; + } + + /** + * Applies smoothing groups to vertex normals. + */ + public static IrMesh applySmoothingGroups(IrMesh mesh) { + return null; + } + + private static void toTangentsWithParity(IrVertex vertex) { + if (vertex.tang != null && vertex.bitang != null) { + float wCoord = vertex.norm.cross(vertex.tang).dot(vertex.bitang) < 0f ? -1f : 1f; + vertex.tang4d = new Vector4f(vertex.tang.x, vertex.tang.y, vertex.tang.z, wCoord); + vertex.tang = null; + vertex.bitang = null; + } + } + + public static void toTangentsWithParity(IrMesh mesh) { + for (IrPolygon polygon : mesh.polygons) { + for (IrVertex vertex : polygon.vertices) { + toTangentsWithParity(vertex); + } + } + } + + private static void trimBoneWeights(IrVertex vertex) { + if (vertex.boneWeightsIndices == null) { + return; + } + + IrBoneWeightIndex[] boneWeightsIndices = vertex.boneWeightsIndices; + + if (boneWeightsIndices.length <= 4) { + return; + } + + // Sort by weight + boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, boneWeightsIndices.length); + Arrays.sort(boneWeightsIndices); + + // Trim to four weights at most + boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, 4); + + // Renormalize weights + float sum = 0; + + for (int i = 0; i < boneWeightsIndices.length; i++) { + sum += boneWeightsIndices[i].boneWeight; + } + + if (sum != 1f) { + float sumToB = sum == 0 ? 0 : 1f / sum; + for (int i = 0; i < boneWeightsIndices.length; i++) { + IrBoneWeightIndex original = boneWeightsIndices[i]; + boneWeightsIndices[i] = new IrBoneWeightIndex(original.boneIndex, original.boneWeight * sumToB); + } + } + + vertex.boneWeightsIndices = boneWeightsIndices; + } + + /** + * Removes low bone weights from mesh, leaving only 4 bone weights at max. + */ + public static void trimBoneWeights(IrMesh mesh) { + for (IrPolygon polygon : mesh.polygons) { + for (IrVertex vertex : polygon.vertices) { + trimBoneWeights(vertex); + } + } + } + + /** + * Convert mesh from quads / triangles to triangles only. + */ + public static void triangulate(IrMesh mesh) { + List newPolygons = new ArrayList(mesh.polygons.length); + for (IrPolygon inputPoly : mesh.polygons) { + if (inputPoly.vertices.length == 4) { + IrPolygon[] tris = quadToTri(inputPoly); + newPolygons.add(tris[0]); + newPolygons.add(tris[1]); + } else if (inputPoly.vertices.length == 3) { + newPolygons.add(inputPoly); + } else { + // N-gon. We have to ignore it.. + logger.log(Level.WARNING, "N-gon encountered, ignoring. " + + "The mesh may not appear correctly. " + + "Triangulate your model prior to export."); + } + } + mesh.polygons = new IrPolygon[newPolygons.size()]; + newPolygons.toArray(mesh.polygons); + } + + /** + * Separate mesh with multiple materials into multiple meshes each with + * one material each. + * + * Polygons without a material will be added to key = -1. + */ + public static IntMap splitByMaterial(IrMesh mesh) { + IntMap> materialToPolyList = new IntMap>(); + for (IrPolygon polygon : mesh.polygons) { + int materialIndex = -1; + for (IrVertex vertex : polygon.vertices) { + if (vertex.material == null) { + continue; + } + if (materialIndex == -1) { + materialIndex = vertex.material; + } else if (materialIndex != vertex.material) { + throw new UnsupportedOperationException("Multiple materials " + + "assigned to the same polygon"); + } + } + List polyList = materialToPolyList.get(materialIndex); + if (polyList == null) { + polyList = new ArrayList(); + materialToPolyList.put(materialIndex, polyList); + } + polyList.add(polygon); + } + IntMap materialToMesh = new IntMap(); + for (IntMap.Entry> entry : materialToPolyList) { + int key = entry.getKey(); + List polygons = entry.getValue(); + if (polygons.size() > 0) { + IrMesh newMesh = new IrMesh(); + newMesh.polygons = new IrPolygon[polygons.size()]; + polygons.toArray(newMesh.polygons); + materialToMesh.put(key, newMesh); + } + } + return materialToMesh; + } + + /** + * Convert IrMesh to jME3 mesh. + */ + public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) { + Map vertexToVertexIndex = new HashMap(); + List vertices = new ArrayList(); + List indexes = new ArrayList(); + + int vertexIndex = 0; + for (IrPolygon polygon : mesh.polygons) { + if (polygon.vertices.length != 3) { + throw new UnsupportedOperationException("IrMesh must be triangulated first"); + } + for (IrVertex vertex : polygon.vertices) { + // Is this vertex already indexed? + Integer existingIndex = vertexToVertexIndex.get(vertex); + if (existingIndex == null) { + // Not indexed yet, allocate index. + indexes.add(vertexIndex); + vertexToVertexIndex.put(vertex, vertexIndex); + vertices.add(vertex); + vertexIndex++; + } else { + // Index already allocated for this vertex, reuse it. + indexes.add(existingIndex); + } + } + } + + Mesh jmeMesh = new Mesh(); + jmeMesh.setMode(Mesh.Mode.Triangles); + + FloatBuffer posBuf = null; + FloatBuffer normBuf = null; + FloatBuffer tangBuf = null; + FloatBuffer uv0Buf = null; + FloatBuffer uv1Buf = null; + ByteBuffer colorBuf = null; + ByteBuffer boneIndices = null; + FloatBuffer boneWeights = null; + IndexBuffer indexBuf = null; + + IrVertex inspectionVertex = vertices.get(0); + if (inspectionVertex.pos != null) { + posBuf = BufferUtils.createVector3Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); + } + if (inspectionVertex.norm != null) { + normBuf = BufferUtils.createVector3Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); + } + if (inspectionVertex.tang4d != null) { + tangBuf = BufferUtils.createFloatBuffer(vertices.size() * 4); + jmeMesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangBuf); + } + if (inspectionVertex.tang != null || inspectionVertex.bitang != null) { + throw new IllegalStateException("Mesh is using 3D tangents, must be converted to 4D tangents first."); + } + if (inspectionVertex.uv0 != null) { + uv0Buf = BufferUtils.createVector2Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv0Buf); + } + if (inspectionVertex.uv1 != null) { + uv1Buf = BufferUtils.createVector2Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.TexCoord2, 2, uv1Buf); + } + if (inspectionVertex.color != null) { + colorBuf = BufferUtils.createByteBuffer(vertices.size() * 4); + jmeMesh.setBuffer(VertexBuffer.Type.Color, 4, colorBuf); + jmeMesh.getBuffer(VertexBuffer.Type.Color).setNormalized(true); + } + if (inspectionVertex.boneWeightsIndices != null) { + boneIndices = BufferUtils.createByteBuffer(vertices.size() * 4); + boneWeights = BufferUtils.createFloatBuffer(vertices.size() * 4); + jmeMesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndices); + jmeMesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeights); + + //creating empty buffers for HW skinning + //the buffers will be setup if ever used. + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + //setting usage to cpuOnly so that the buffer is not send empty to the GPU + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + + jmeMesh.setBuffer(weightsHW); + jmeMesh.setBuffer(indicesHW); + } + if (vertices.size() >= 65536) { + // too many verticies: use intbuffer instead of shortbuffer + IntBuffer ib = BufferUtils.createIntBuffer(indexes.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, ib); + indexBuf = new IndexIntBuffer(ib); + } else { + ShortBuffer sb = BufferUtils.createShortBuffer(indexes.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, sb); + indexBuf = new IndexShortBuffer(sb); + } + + jmeMesh.setStatic(); + + int maxBonesPerVertex = -1; + + for (IrVertex vertex : vertices) { + if (posBuf != null) { + posBuf.put(vertex.pos.x).put(vertex.pos.y).put(vertex.pos.z); + } + if (normBuf != null) { + normBuf.put(vertex.norm.x).put(vertex.norm.y).put(vertex.norm.z); + } + if (tangBuf != null) { + tangBuf.put(vertex.tang4d.x).put(vertex.tang4d.y).put(vertex.tang4d.z).put(vertex.tang4d.w); + } + if (uv0Buf != null) { + uv0Buf.put(vertex.uv0.x).put(vertex.uv0.y); + } + if (uv1Buf != null) { + uv1Buf.put(vertex.uv1.x).put(vertex.uv1.y); + } + if (colorBuf != null) { + colorBuf.putInt(vertex.color.asIntABGR()); + } + if (boneIndices != null) { + if (vertex.boneWeightsIndices != null) { + if (vertex.boneWeightsIndices.length > 4) { + throw new UnsupportedOperationException("Mesh uses more than 4 weights per bone. " + + "Call trimBoneWeights() to allieviate this"); + } + for (int i = 0; i < vertex.boneWeightsIndices.length; i++) { + boneIndices.put((byte) (vertex.boneWeightsIndices[i].boneIndex & 0xFF)); + boneWeights.put(vertex.boneWeightsIndices[i].boneWeight); + } + for (int i = 0; i < 4 - vertex.boneWeightsIndices.length; i++) { + boneIndices.put((byte)0); + boneWeights.put(0f); + } + } else { + boneIndices.putInt(0); + boneWeights.put(0f).put(0f).put(0f).put(0f); + } + + maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length); + } + } + + for (int i = 0; i < indexes.size(); i++) { + indexBuf.put(i, indexes.get(i)); + } + + jmeMesh.updateCounts(); + jmeMesh.updateBound(); + + if (boneIndices != null) { + jmeMesh.setMaxNumWeights(maxBonesPerVertex); + jmeMesh.prepareForAnim(true); + jmeMesh.generateBindPose(true); + } + + return jmeMesh; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java new file mode 100644 index 000000000..5870846a1 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import java.util.Arrays; + +public class IrVertex implements Cloneable { + + public Vector3f pos; + public Vector3f norm; + public Vector4f tang4d; + public Vector3f tang; + public Vector3f bitang; + public Vector2f uv0; + public Vector2f uv1; + public ColorRGBA color; + public Integer material; + public Integer smoothing; + public IrBoneWeightIndex[] boneWeightsIndices; + + public IrVertex deepClone() { + IrVertex v = new IrVertex(); + v.pos = pos != null ? pos.clone() : null; + v.norm = norm != null ? norm.clone() : null; + v.tang4d = tang4d != null ? tang4d.clone() : null; + v.tang = tang != null ? tang.clone() : null; + v.bitang = bitang != null ? bitang.clone() : null; + v.uv0 = uv0 != null ? uv0.clone() : null; + v.uv1 = uv1 != null ? uv1.clone() : null; + v.color = color != null ? color.clone() : null; + v.material = material; + v.smoothing = smoothing; + if (boneWeightsIndices != null) { + v.boneWeightsIndices = new IrBoneWeightIndex[boneWeightsIndices.length]; + for (int i = 0; i < boneWeightsIndices.length; i++) { + v.boneWeightsIndices[i] = (IrBoneWeightIndex) boneWeightsIndices[i].clone(); + } + } + return v; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 73 * hash + (this.pos != null ? this.pos.hashCode() : 0); + hash = 73 * hash + (this.norm != null ? this.norm.hashCode() : 0); + hash = 73 * hash + (this.tang4d != null ? this.tang4d.hashCode() : 0); + hash = 73 * hash + (this.tang != null ? this.tang.hashCode() : 0); + hash = 73 * hash + (this.uv0 != null ? this.uv0.hashCode() : 0); + hash = 73 * hash + (this.uv1 != null ? this.uv1.hashCode() : 0); + hash = 73 * hash + (this.color != null ? this.color.hashCode() : 0); + hash = 73 * hash + (this.material != null ? this.material.hashCode() : 0); + hash = 73 * hash + (this.smoothing != null ? this.smoothing.hashCode() : 0); + hash = 73 * hash + Arrays.deepHashCode(this.boneWeightsIndices); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IrVertex other = (IrVertex) obj; + if (this.pos != other.pos && (this.pos == null || !this.pos.equals(other.pos))) { + return false; + } + if (this.norm != other.norm && (this.norm == null || !this.norm.equals(other.norm))) { + return false; + } + if (this.tang4d != other.tang4d && (this.tang4d == null || !this.tang4d.equals(other.tang4d))) { + return false; + } + if (this.tang != other.tang && (this.tang == null || !this.tang.equals(other.tang))) { + return false; + } + if (this.uv0 != other.uv0 && (this.uv0 == null || !this.uv0.equals(other.uv0))) { + return false; + } + if (this.uv1 != other.uv1 && (this.uv1 == null || !this.uv1.equals(other.uv1))) { + return false; + } + if (this.color != other.color && (this.color == null || !this.color.equals(other.color))) { + return false; + } + if (this.material != other.material && (this.material == null || !this.material.equals(other.material))) { + return false; + } + if (this.smoothing != other.smoothing && (this.smoothing == null || !this.smoothing.equals(other.smoothing))) { + return false; + } + if (!Arrays.deepEquals(this.boneWeightsIndices, other.boneWeightsIndices)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Vertex { "); + + if (pos != null) { + sb.append("pos=").append(pos).append(", "); + } + if (norm != null) { + sb.append("norm=").append(pos).append(", "); + } + if (tang != null) { + sb.append("tang=").append(pos).append(", "); + } + if (uv0 != null) { + sb.append("uv0=").append(pos).append(", "); + } + if (uv1 != null) { + sb.append("uv1=").append(pos).append(", "); + } + if (color != null) { + sb.append("color=").append(pos).append(", "); + } + if (material != null) { + sb.append("material=").append(pos).append(", "); + } + if (smoothing != null) { + sb.append("smoothing=").append(pos).append(", "); + } + + if (sb.toString().endsWith(", ")) { + sb.delete(sb.length() - 2, sb.length()); + } + + sb.append(" }"); + return sb.toString(); + } +} 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 index 32138270d..0ede52884 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java @@ -40,6 +40,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; /** * Part of the jME XML IO system as introduced in the google code jmexml project. @@ -61,7 +62,8 @@ public class XMLExporter implements JmeExporter { } - public boolean save(Savable object, OutputStream f) throws IOException { + @Override + public void 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); @@ -69,18 +71,22 @@ public class XMLExporter implements JmeExporter { 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; + } catch (ParserConfigurationException ex) { + throw new IOException(ex); } } - public boolean save(Savable object, File f) throws IOException { - return save(object, new FileOutputStream(f)); + @Override + public void save(Savable object, File f) throws IOException { + FileOutputStream fos = new FileOutputStream(f); + try { + save(object, fos); + } finally { + fos.close(); + } } + @Override public OutputCapsule getCapsule(Savable object) { return domOut; } diff --git a/sdk/build.gradle b/sdk/build.gradle index 745b878c6..b70e80b13 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -48,7 +48,7 @@ task checkPlatformConfig { def platformFile = file("nbproject/private/platform-private.properties") if(!platformFile.exists()){ def netbeansFolder = file("../netbeans") - if(!netbeansFolder.exists()){ + if(!netbeansFolder.exists() || netbeansFolder.list().length == 0){ println "Downloading NetBeans Platform base, this only has to be done once.." def f = file("netbeans.zip") new URL(netbeansUrl).withInputStream{ i -> f.withOutputStream{ it << i }} diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java b/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java index 0fdabe192..6ced1f1f9 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java @@ -102,7 +102,7 @@ public final class SNDefWizardIterator implements WizardDescriptor.Instantiating //Get the template and convert it: FileObject tplSnd = Templates.getTemplate(wizard); - FileObject tplShd = tplSnd.getParent().getChildren()[1]; + FileObject tplShd = tplSnd.getParent().getChildren()[0]; DataObject templateSnd = DataObject.find(tplSnd); DataObject templateShd = DataObject.find(tplShd); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties index 86f1414f9..43469051d 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties @@ -44,8 +44,8 @@ SceneComposerTopComponent.scaleButton.text= SceneComposerTopComponent.selectButton.text= SceneComposerTopComponent.selectButton.toolTipText=Select SceneComposerTopComponent.moveButton.toolTipText=Move -SceneComposerTopComponent.rotateButton.toolTipText=Rotate (in-development) -SceneComposerTopComponent.scaleButton.toolTipText=Scale (in-development) +SceneComposerTopComponent.rotateButton.toolTipText=Rotate +SceneComposerTopComponent.scaleButton.toolTipText=Scale SceneComposerTopComponent.sceneInfoPanel.border.title=no scene loaded SceneComposerTopComponent.jLabel5.text=Effects : SceneComposerTopComponent.emitButton.toolTipText=Emit all particles of all particle emitters from the selected Node @@ -63,3 +63,5 @@ SceneComposerTopComponent.jToggleScene.toolTipText=Snap to Scene SceneComposerTopComponent.jToggleGrid.toolTipText=Snap to Grid SceneComposerTopComponent.jToggleSelectGeom.toolTipText=Select Geometries SceneComposerTopComponent.jToggleSelectTerrain.toolTipText=Select Terrain +SceneComposerTopComponent.scaleButton.AccessibleContext.accessibleDescription=Scale +SceneComposerTopComponent.transformationTypeComboBox.toolTipText=Choose the transformation type used by tools. diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java index dff940e50..50b6d5cfe 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java @@ -10,6 +10,7 @@ import com.jme3.gde.core.scene.SceneApplication; import com.jme3.gde.core.scene.controller.SceneToolController; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.scenecomposer.tools.PickManager; import com.jme3.input.event.KeyInputEvent; import com.jme3.light.Light; import com.jme3.light.PointLight; @@ -50,7 +51,12 @@ public class SceneComposerToolController extends SceneToolController { private boolean snapToScene = false; private boolean selectTerrain = false; private boolean selectGeometries = false; - + private TransformationType transformationType = TransformationType.local; + + public enum TransformationType { + local, global, camera + } + public SceneComposerToolController(final Node toolsNode, AssetManager manager, JmeNode rootNode) { super(toolsNode, manager); this.rootNode = rootNode; @@ -347,9 +353,39 @@ public class SceneComposerToolController extends SceneToolController { public void setSelectGeometries(boolean selectGeometries) { this.selectGeometries = selectGeometries; } + + public void setTransformationType(String type) { + if(type != null){ + if(type.equals("Local")){ + setTransformationType(TransformationType.local); + } else if(type.equals("Global")){ + setTransformationType(TransformationType.global); + } else if(type.equals("Camera")){ + setTransformationType(TransformationType.camera); + } + } + } + + /** + * @param type the transformationType to set + */ + public void setTransformationType(TransformationType type) { + if(type != this.transformationType){ + this.transformationType = type; + if(editTool != null){ + //update the transform type of the tool + editTool.setTransformType(transformationType); + } + } + } - - + /** + * @return the transformationType + */ + public TransformationType getTransformationType() { + return transformationType; + } + /** * A marker on the screen that shows where a point light or * a spot light is. This marker is not part of the scene, diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form index 8b21fb202..fd4686f24 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form @@ -99,6 +99,25 @@ + + + + + + + + + + + + + + + + + + + @@ -184,6 +203,11 @@ + + + + + diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java index 1259d74a9..b06e126dc 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java @@ -97,6 +97,8 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce sceneInfoLabel1 = new javax.swing.JLabel(); sceneInfoLabel2 = new javax.swing.JLabel(); jToolBar1 = new javax.swing.JToolBar(); + transformationTypeComboBox = new javax.swing.JComboBox(); + jSeparator9 = new javax.swing.JToolBar.Separator(); selectButton = new javax.swing.JToggleButton(); moveButton = new javax.swing.JToggleButton(); rotateButton = new javax.swing.JToggleButton(); @@ -165,6 +167,16 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce jToolBar1.setFloatable(false); jToolBar1.setRollover(true); + transformationTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Local", "Global", "Camera" })); + transformationTypeComboBox.setToolTipText(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.transformationTypeComboBox.toolTipText")); // NOI18N + transformationTypeComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + transformationTypeComboBoxActionPerformed(evt); + } + }); + jToolBar1.add(transformationTypeComboBox); + jToolBar1.add(jSeparator9); + spatialModButtonGroup.add(selectButton); selectButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/icon_select.png"))); // NOI18N selectButton.setSelected(true); @@ -221,6 +233,8 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce } }); jToolBar1.add(scaleButton); + scaleButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.scaleButton.AccessibleContext.accessibleDescription")); // NOI18N + jToolBar1.add(jSeparator5); jToggleScene.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/snapScene.png"))); // NOI18N @@ -644,6 +658,11 @@ private void jToggleSelectTerrainActionPerformed(java.awt.event.ActionEvent evt) private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleSelectGeomActionPerformed toolController.setSelectGeometries(jToggleSelectGeom.isSelected()); }//GEN-LAST:event_jToggleSelectGeomActionPerformed + + private void transformationTypeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_transformationTypeComboBoxActionPerformed + toolController.setTransformationType((String)transformationTypeComboBox.getSelectedItem()); + }//GEN-LAST:event_transformationTypeComboBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton camToCursorSelectionButton; private javax.swing.JButton createPhysicsMeshButton; @@ -671,6 +690,7 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/ private javax.swing.JSeparator jSeparator6; private javax.swing.JToolBar.Separator jSeparator7; private javax.swing.JToolBar.Separator jSeparator8; + private javax.swing.JToolBar.Separator jSeparator9; private javax.swing.JTextField jTextField1; private javax.swing.JToggleButton jToggleGrid; private javax.swing.JToggleButton jToggleScene; @@ -692,6 +712,7 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/ private javax.swing.JToggleButton showGridToggleButton; private javax.swing.JToggleButton showSelectionToggleButton; private javax.swing.ButtonGroup spatialModButtonGroup; + private javax.swing.JComboBox transformationTypeComboBox; // End of variables declaration//GEN-END:variables private void emit(Spatial root) { diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java index d2767781c..372984f8e 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java @@ -57,6 +57,7 @@ public abstract class SceneEditTool { protected Node axisMarker; protected Material redMat, blueMat, greenMat, yellowMat, cyanMat, magentaMat, orangeMat; protected Geometry quadXY, quadXZ, quadYZ; + protected SceneComposerToolController.TransformationType transformType; protected enum AxisMarkerPickType { @@ -72,6 +73,7 @@ public abstract class SceneEditTool { public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { this.manager = manager; this.toolController = toolController; + this.setTransformType(toolController.getTransformationType()); //this.selectedSpatial = selectedSpatial; addMarker(toolNode, onTopToolNode); } @@ -130,7 +132,19 @@ public abstract class SceneEditTool { public void doUpdateToolsTransformation() { if (toolController.getSelectedSpatial() != null) { axisMarker.setLocalTranslation(toolController.getSelectedSpatial().getWorldTranslation()); - axisMarker.setLocalRotation(toolController.getSelectedSpatial().getLocalRotation()); + switch (transformType) { + case local: + axisMarker.setLocalRotation(toolController.getSelectedSpatial().getLocalRotation()); + break; + case global: + axisMarker.setLocalRotation(Quaternion.IDENTITY); + break; + case camera: + if(camera != null){ + axisMarker.setLocalRotation(camera.getRotation()); + } + break; + } setAxisMarkerScale(toolController.getSelectedSpatial()); } else { axisMarker.setLocalTranslation(Vector3f.ZERO); @@ -285,7 +299,7 @@ public abstract class SceneEditTool { * what part of the axis was selected. * For example if (1,0,0) is returned, then the X-axis pole was selected. * If (0,1,1) is returned, then the Y-Z plane was selected. - * + * * @return null if it did not intersect the marker */ protected Vector3f pickAxisMarker(Camera cam, Vector2f mouseLoc, AxisMarkerPickType pickType) { @@ -336,7 +350,7 @@ public abstract class SceneEditTool { CollisionResults results = new CollisionResults(); Ray ray = new Ray(); Vector3f pos = cam.getWorldCoordinates(mouseLoc, 0).clone(); - Vector3f dir = cam.getWorldCoordinates(mouseLoc, 0.1f).clone(); + Vector3f dir = cam.getWorldCoordinates(mouseLoc, 0.125f).clone(); dir.subtractLocal(pos).normalizeLocal(); ray.setOrigin(pos); ray.setDirection(dir); @@ -347,7 +361,7 @@ public abstract class SceneEditTool { /** * Show what axis or plane the mouse is currently over and will affect. - * @param axisMarkerPickType + * @param axisMarkerPickType */ protected void highlightAxisMarker(Camera camera, Vector2f screenCoord, AxisMarkerPickType axisMarkerPickType) { highlightAxisMarker(camera, screenCoord, axisMarkerPickType, false); @@ -355,12 +369,12 @@ public abstract class SceneEditTool { /** * Show what axis or plane the mouse is currently over and will affect. - * @param axisMarkerPickType + * @param axisMarkerPickType * @param colorAll highlight all parts of the marker when only one is selected */ protected void highlightAxisMarker(Camera camera, Vector2f screenCoord, AxisMarkerPickType axisMarkerPickType, boolean colorAll) { setDefaultAxisMarkerColors(); - Vector3f picked = pickAxisMarker(camera, screenCoord, axisPickType); + Vector3f picked = pickAxisMarker(camera, screenCoord, axisMarkerPickType); if (picked == null) { return; } @@ -371,16 +385,17 @@ public abstract class SceneEditTool { axisMarker.getChild("arrowY").setMaterial(orangeMat); } else if (picked == ARROW_Z) { axisMarker.getChild("arrowZ").setMaterial(orangeMat); - } + } else { - if (picked == QUAD_XY || colorAll) { - axisMarker.getChild("quadXY").setMaterial(orangeMat); - } - if (picked == QUAD_XZ || colorAll) { - axisMarker.getChild("quadXZ").setMaterial(orangeMat); - } - if (picked == QUAD_YZ || colorAll) { - axisMarker.getChild("quadYZ").setMaterial(orangeMat); + if (picked == QUAD_XY || colorAll) { + axisMarker.getChild("quadXY").setMaterial(orangeMat); + } + if (picked == QUAD_XZ || colorAll) { + axisMarker.getChild("quadXZ").setMaterial(orangeMat); + } + if (picked == QUAD_YZ || colorAll) { + axisMarker.getChild("quadYZ").setMaterial(orangeMat); + } } } @@ -453,6 +468,7 @@ public abstract class SceneEditTool { // axis.attachChild(quadYZ); axis.setModelBound(new BoundingBox()); + axis.updateModelBound(); return axis; } @@ -485,4 +501,12 @@ public abstract class SceneEditTool { public void setCamera(Camera camera) { this.camera = camera; } + + public SceneComposerToolController.TransformationType getTransformType() { + return transformType; + } + + public void setTransformType(SceneComposerToolController.TransformationType transformType) { + this.transformType = transformType; + } } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java index 97f48cc9f..b3a48a6f6 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java @@ -5,8 +5,11 @@ package com.jme3.gde.scenecomposer.tools; import com.jme3.asset.AssetManager; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.SceneEditTool; import com.jme3.math.Vector2f; @@ -17,19 +20,21 @@ import org.openide.loaders.DataObject; import org.openide.util.Lookup; /** - * Move an object. - * When created, it generates a quad that will lie along a plane - * that the user selects for moving on. When the mouse is over - * the axisMarker, it will highlight the plane that it is over: XY,XZ,YZ. - * When clicked and then dragged, the selected object will move along that - * plane. + * Move an object. When created, it generates a quad that will lie along a plane + * that the user selects for moving on. When the mouse is over the axisMarker, + * it will highlight the plane that it is over: XY,XZ,YZ. When clicked and then + * dragged, the selected object will move along that plane. + * * @author Brent Owens */ public class MoveTool extends SceneEditTool { - private Vector3f pickedPlane; + private Vector3f pickedMarker; + private Vector3f constraintAxis; //used for one axis move private boolean wasDragging = false; - private MoveManager moveManager; + private Vector3f startPosition; + private Vector3f lastPosition; + private PickManager pickManager; public MoveTool() { axisPickType = AxisMarkerPickType.axisAndPlane; @@ -40,7 +45,7 @@ public class MoveTool extends SceneEditTool { @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); - moveManager = Lookup.getDefault().lookup(MoveManager.class); + pickManager = Lookup.getDefault().lookup(PickManager.class); displayPlanes(); } @@ -48,27 +53,61 @@ public class MoveTool extends SceneEditTool { public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint if (wasDragging) { - actionPerformed(moveManager.makeUndo()); + actionPerformed(new MoveUndo(toolController.getSelectedSpatial(), startPosition, lastPosition)); wasDragging = false; } - moveManager.reset(); + pickManager.reset(); + } else { + if (toolController.getSelectedSpatial() == null) { + return; + } + + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { + return; + } + + if (pickedMarker.equals(QUAD_XY)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_XZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(ARROW_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_X; // move only X + } else if (pickedMarker.equals(ARROW_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Y; // move only Y + } else if (pickedMarker.equals(ARROW_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Z; // move only Z + } + startPosition = toolController.getSelectedSpatial().getLocalTranslation().clone(); + wasDragging = true; + } } } @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + cancel(); + } } @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { - - if (pickedPlane == null) { + + if (pickedMarker == null) { highlightAxisMarker(camera, screenCoord, axisPickType); } else { - pickedPlane = null; - moveManager.reset(); + pickedMarker = null; + pickManager.reset(); } } @@ -76,42 +115,92 @@ public class MoveTool extends SceneEditTool { public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint if (wasDragging) { - actionPerformed(moveManager.makeUndo()); + actionPerformed(new MoveUndo(toolController.getSelectedSpatial(), startPosition, lastPosition)); wasDragging = false; } - moveManager.reset(); - return; + pickManager.reset(); + } else if (wasDragging == true) { + if (!pickManager.updatePick(camera, screenCoord)) { + return; + } + Vector3f diff = Vector3f.ZERO; + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + diff = pickManager.getTranslation(); + + } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { + diff = pickManager.getTranslation(constraintAxis); + } + Vector3f position = startPosition.add(diff); + lastPosition = position; + toolController.getSelectedSpatial().setLocalTranslation(position); + updateToolsTransformation(); } + } - if (toolController.getSelectedSpatial() == null) { - return; + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); } + } - if (pickedPlane == null) { - pickedPlane = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedPlane == null) { - return; - } + private void cancel() { + if (wasDragging) { + wasDragging = false; + toolController.getSelectedSpatial().setLocalTranslation(startPosition); + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint + pickManager.reset(); + } + } - if (pickedPlane.equals(new Vector3f(1, 1, 0))) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XY, true); - } else if (pickedPlane.equals(new Vector3f(1, 0, 1))) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XZ, true); - } else if (pickedPlane.equals(new Vector3f(0, 1, 1))) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.YZ, true); + protected class MoveUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + private Vector3f before = new Vector3f(), after = new Vector3f(); + + MoveUndo(Spatial spatial, Vector3f before, Vector3f after) { + this.spatial = spatial; + this.before.set(before); + if (after != null) { + this.after.set(after); } } - if (!moveManager.move(camera, screenCoord)) { - return; + + @Override + public void sceneUndo() { + spatial.setLocalTranslation(before); + RigidBodyControl control = spatial.getControl(RigidBodyControl.class); + if (control != null) { + control.setPhysicsLocation(spatial.getWorldTranslation()); + } + CharacterControl character = spatial.getControl(CharacterControl.class); + if (character != null) { + character.setPhysicsLocation(spatial.getWorldTranslation()); + } + // toolController.selectedSpatialTransformed(); } - updateToolsTransformation(); - wasDragging = true; - } + @Override + public void sceneRedo() { + spatial.setLocalTranslation(after); + RigidBodyControl control = spatial.getControl(RigidBodyControl.class); + if (control != null) { + control.setPhysicsLocation(spatial.getWorldTranslation()); + } + CharacterControl character = spatial.getControl(CharacterControl.class); + if (character != null) { + character.setPhysicsLocation(spatial.getWorldTranslation()); + } + //toolController.selectedSpatialTransformed(); + } - @Override - public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + public void setAfter(Vector3f after) { + this.after.set(after); + } } } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java new file mode 100644 index 000000000..22fa8478a --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java @@ -0,0 +1,193 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools; + +import com.jme3.gde.scenecomposer.SceneComposerToolController; +import com.jme3.gde.scenecomposer.SceneEditTool; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author dokthar + */ +@ServiceProvider(service = PickManager.class) +public class PickManager { + + private Vector3f startPickLoc; + private Vector3f finalPickLoc; + private Vector3f startSpatialLocation; + private Quaternion origineRotation; + private final Node plane; + private Spatial spatial; + private SceneComposerToolController.TransformationType transformationType; + + protected static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); + protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 + protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 + + + public PickManager() { + float size = 1000; + Geometry g = new Geometry("plane", new Quad(size, size)); + g.setLocalTranslation(-size / 2, -size / 2, 0); + plane = new Node(); + plane.attachChild(g); + } + + public void reset() { + startPickLoc = null; + finalPickLoc = null; + startSpatialLocation = null; + spatial = null; + } + + public void initiatePick(Spatial selectedSpatial, Quaternion planeRotation, SceneComposerToolController.TransformationType type, Camera camera, Vector2f screenCoord) { + spatial = selectedSpatial; + startSpatialLocation = selectedSpatial.getWorldTranslation().clone(); + + setTransformation(planeRotation, type, camera); + plane.setLocalTranslation(startSpatialLocation); + + startPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); + } + + public void setTransformation(Quaternion planeRotation, SceneComposerToolController.TransformationType type, Camera camera) { + Quaternion rot = new Quaternion(); + transformationType = type; + if (transformationType == SceneComposerToolController.TransformationType.local) { + rot.set(spatial.getWorldRotation()); + rot.multLocal(planeRotation); + origineRotation = spatial.getWorldRotation().clone(); + } else if (transformationType == SceneComposerToolController.TransformationType.global) { + rot.set(planeRotation); + origineRotation = new Quaternion(Quaternion.IDENTITY); + } else if (transformationType == SceneComposerToolController.TransformationType.camera) { + rot.set(camera.getRotation()); + origineRotation = camera.getRotation().clone(); + } + plane.setLocalRotation(rot); + } + + /** + * + * @param camera + * @param screenCoord + * @return true if the the new picked location is set, else return false. + */ + public boolean updatePick(Camera camera, Vector2f screenCoord) { + if(transformationType == SceneComposerToolController.TransformationType.camera){ + origineRotation = camera.getRotation(); + plane.setLocalRotation(camera.getRotation()); + } + finalPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); + return finalPickLoc != null; + } + + /** + * + * @return the start location in WorldSpace + */ + public Vector3f getStartLocation() { + return startSpatialLocation; + } + + /** + * + * @return the vector from the tool origin to the start location, in + * WorldSpace + */ + public Vector3f getStartOffset() { + return startPickLoc.subtract(startSpatialLocation); + } + + /** + * + * @return the vector from the tool origin to the final location, in + * WorldSpace + */ + public Vector3f getFinalOffset() { + return finalPickLoc.subtract(startSpatialLocation); + } + + /** + * + * @return the angle between the start location and the final location + */ + public float getAngle() { + Vector3f v1, v2; + v1 = startPickLoc.subtract(startSpatialLocation); + v2 = finalPickLoc.subtract(startSpatialLocation); + return v1.angleBetween(v2); + } + + /** + * + * @return the Quaternion rotation in the WorldSpace + */ + public Quaternion getRotation() { + return getRotation(Quaternion.IDENTITY); + } + + /** + * + * @return the Quaternion rotation in the ToolSpace + */ + public Quaternion getLocalRotation() { + return getRotation(origineRotation.inverse()); + } + + /** + * Get the Rotation into a specific custom space. + * @param transforme the rotation to the custom space (World to Custom space) + * @return the Rotation in the custom space + */ + public Quaternion getRotation(Quaternion transforme) { + Vector3f v1, v2; + v1 = transforme.mult(startPickLoc.subtract(startSpatialLocation).normalize()); + v2 = transforme.mult(finalPickLoc.subtract(startSpatialLocation).normalize()); + Vector3f axis = v1.cross(v2); + float angle = v1.angleBetween(v2); + return new Quaternion().fromAngleAxis(angle, axis); + } + + /** + * + * @return the translation in WorldSpace + */ + public Vector3f getTranslation() { + return finalPickLoc.subtract(startPickLoc); + } + + /** + * + * @param axisConstrainte + * @return + */ + public Vector3f getTranslation(Vector3f axisConstrainte) { + Vector3f localConstrainte = (origineRotation.mult(axisConstrainte)).normalize(); // according to the "plane" rotation + Vector3f constrainedTranslation = localConstrainte.mult(getTranslation().dot(localConstrainte)); + return constrainedTranslation; + } + + /** + * + * @param axisConstrainte + * @return + */ + public Vector3f getLocalTranslation(Vector3f axisConstrainte) { + return getTranslation(origineRotation.inverse().mult(axisConstrainte)); + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java index 3c4e27b95..21e9a56d8 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java @@ -16,144 +16,145 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import org.openide.loaders.DataObject; - +import org.openide.util.Lookup; + /** * * @author kbender */ public class RotateTool extends SceneEditTool { - - private Vector3f pickedPlane; + + private Vector3f pickedMarker; private Vector2f lastScreenCoord; private Quaternion startRotate; private Quaternion lastRotate; private boolean wasDragging = false; - + private PickManager pickManager; + public RotateTool() { - axisPickType = AxisMarkerPickType.axisAndPlane; + axisPickType = AxisMarkerPickType.planeOnly; setOverrideCameraControl(true); } - + @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); + pickManager = Lookup.getDefault().lookup(PickManager.class); displayPlanes(); } - + @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection lastScreenCoord = null; if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); wasDragging = false; } + pickManager.reset(); + } else { + if (toolController.getSelectedSpatial() == null) { + return; + } + + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { + return; + } + + if (pickedMarker.equals(QUAD_XY)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_XZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + } + startRotate = toolController.getSelectedSpatial().getLocalRotation().clone(); + wasDragging = true; + } } } - + @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - + if (pressed) { + cancel(); + } } - + @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { - if (pickedPlane == null) { + if (pickedMarker == null) { highlightAxisMarker(camera, screenCoord, axisPickType); - } - else { - pickedPlane = null; + } else { + pickedMarker = null; + pickManager.reset(); } } - + @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection lastScreenCoord = null; - + if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); wasDragging = false; } - return; - } - - if (toolController.getSelectedSpatial() == null) - { - return; - } - - if (pickedPlane == null) - { - pickedPlane = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedPlane == null) - { + pickManager.reset(); + } else if (wasDragging) { + if (!pickManager.updatePick(camera, screenCoord)) { return; } - startRotate = toolController.getSelectedSpatial().getLocalRotation().clone(); - } - - if (lastScreenCoord == null) { - lastScreenCoord = screenCoord; - } else { - Quaternion rotate = new Quaternion(); - float diff; - if(pickedPlane.equals(QUAD_XY)) - { - diff = -(screenCoord.x-lastScreenCoord.x); - diff *= 0.03f; - rotate = rotate.fromAngleAxis(diff, Vector3f.UNIT_Z); - } - else if(pickedPlane.equals(QUAD_YZ)) - { - diff = -(screenCoord.y-lastScreenCoord.y); - diff *= 0.03f; - rotate = rotate.fromAngleAxis(diff, Vector3f.UNIT_X); - } - else if(pickedPlane.equals(QUAD_XZ)) - { - diff = screenCoord.x-lastScreenCoord.x; - diff *= 0.03f; - rotate = rotate.fromAngleAxis(diff, Vector3f.UNIT_Y); + + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + Quaternion rotation = startRotate.mult(pickManager.getRotation(startRotate.inverse())); + toolController.getSelectedSpatial().setLocalRotation(rotation); + lastRotate = rotation; } - - lastScreenCoord = screenCoord; - Quaternion rotation = toolController.getSelectedSpatial().getLocalRotation().mult(rotate); - lastRotate = rotation; - toolController.getSelectedSpatial().setLocalRotation(rotation); updateToolsTransformation(); } - - wasDragging = true; } - + @Override public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - + if (pressed) { + cancel(); + } } - + + private void cancel() { + if (wasDragging) { + wasDragging = false; + toolController.getSelectedSpatial().setLocalRotation(startRotate); + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + pickManager.reset(); + } + } + private class ScaleUndo extends AbstractUndoableSceneEdit { - + private Spatial spatial; - private Quaternion before,after; - + private Quaternion before, after; + ScaleUndo(Spatial spatial, Quaternion before, Quaternion after) { this.spatial = spatial; this.before = before; this.after = after; } - + @Override public void sceneUndo() { spatial.setLocalRotation(before); toolController.selectedSpatialTransformed(); } - + @Override public void sceneRedo() { spatial.setLocalRotation(after); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java index 862ef2be2..a34478372 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java @@ -10,118 +10,159 @@ import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.SceneEditTool; +import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import org.openide.loaders.DataObject; +import org.openide.util.Lookup; /** * * @author sploreg */ public class ScaleTool extends SceneEditTool { - - private Vector3f pickedPlane; - private Vector2f lastScreenCoord; + + private Vector3f pickedMarker; + private Vector3f constraintAxis; //used for one axis scale private Vector3f startScale; private Vector3f lastScale; private boolean wasDragging = false; - + private PickManager pickManager; + public ScaleTool() { axisPickType = AxisMarkerPickType.axisAndPlane; setOverrideCameraControl(true); } - + @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); + pickManager = Lookup.getDefault().lookup(PickManager.class); displayPlanes(); } - + @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection - lastScreenCoord = null; + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no axis constraint if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startScale, lastScale)); wasDragging = false; } + pickManager.reset(); + } else { + if (toolController.getSelectedSpatial() == null) { + return; + } + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { + return; + } + + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), + SceneComposerToolController.TransformationType.camera, camera, screenCoord); + } else if (pickedMarker.equals(ARROW_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_X; // scale only X + } else if (pickedMarker.equals(ARROW_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Y; // scale only Y + } else if (pickedMarker.equals(ARROW_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Z; // scale only Z + } + startScale = toolController.getSelectedSpatial().getLocalScale().clone(); + wasDragging = true; + } } } - + @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - + if (pressed) { + cancel(); + } } @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { - if (pickedPlane == null) { + if (pickedMarker == null) { highlightAxisMarker(camera, screenCoord, axisPickType, true); + } else { + pickedMarker = null; + pickManager.reset(); } - /*else { - pickedPlane = null; - lastScreenCoord = null; - }*/ } @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection - lastScreenCoord = null; - + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no axis constraint if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startScale, lastScale)); wasDragging = false; } - return; - } - - if (toolController.getSelectedSpatial() == null) - return; - if (pickedPlane == null) { - pickedPlane = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedPlane == null) + pickManager.reset(); + } else if (wasDragging) { + if (!pickManager.updatePick(camera, screenCoord)) { return; - startScale = toolController.getSelectedSpatial().getLocalScale().clone(); - } - - if (lastScreenCoord == null) { - lastScreenCoord = screenCoord; - } else { - float diff = screenCoord.y-lastScreenCoord.y; - diff *= 0.1f; - lastScreenCoord = screenCoord; - Vector3f scale = toolController.getSelectedSpatial().getLocalScale().add(diff, diff, diff); - lastScale = scale; - toolController.getSelectedSpatial().setLocalScale(scale); + } + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + constraintAxis = pickManager.getStartOffset().normalize(); + float diff = pickManager.getTranslation(constraintAxis).dot(constraintAxis); + diff *= 0.5f; + Vector3f scale = startScale.add(new Vector3f(diff, diff, diff)); + lastScale = scale; + toolController.getSelectedSpatial().setLocalScale(scale); + } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { + // Get the translation in the spatial Space + Quaternion worldToSpatial = toolController.getSelectedSpatial().getWorldRotation().inverse(); + Vector3f diff = pickManager.getTranslation(worldToSpatial.mult(constraintAxis)); + diff.multLocal(0.5f); + Vector3f scale = startScale.add(diff); + lastScale = scale; + toolController.getSelectedSpatial().setLocalScale(scale); + } updateToolsTransformation(); } - - wasDragging = true; } @Override public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - + if (pressed) { + cancel(); + } + } + + private void cancel() { + if (wasDragging) { + wasDragging = false; + toolController.getSelectedSpatial().setLocalScale(startScale); + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + pickManager.reset(); + } } private class ScaleUndo extends AbstractUndoableSceneEdit { private Spatial spatial; - private Vector3f before,after; - + private Vector3f before, after; + ScaleUndo(Spatial spatial, Vector3f before, Vector3f after) { this.spatial = spatial; this.before = before; this.after = after; } - + @Override public void sceneUndo() { spatial.setLocalScale(before); @@ -133,6 +174,6 @@ public class ScaleTool extends SceneEditTool { spatial.setLocalScale(after); toolController.selectedSpatialTransformed(); } - + } }