Merge pull request #2 from jMonkeyEngine/master

update fork from jMonkeyEngine to jmecn
empirephoenix-patch-1
Yan 7 years ago committed by GitHub
commit 189c8a5a6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 4
      .travis.yml
  3. 9
      appveyor.yml
  4. 4
      build.gradle
  5. 3
      common.gradle
  6. 4
      gradle.properties
  7. BIN
      gradle/wrapper/gradle-wrapper.jar
  8. 4
      gradle/wrapper/gradle-wrapper.properties
  9. 22
      gradlew
  10. 6
      gradlew.bat
  11. 2
      jme3-android/build.gradle
  12. 4
      jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java
  13. 5
      jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
  14. 10
      jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
  15. 20
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  16. 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  17. 117
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java
  18. 22
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  19. 12
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java
  20. BIN
      jme3-bullet-native/libs/native/linux/x86/libbulletjme.so
  21. BIN
      jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so
  22. BIN
      jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib
  23. BIN
      jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib
  24. BIN
      jme3-bullet-native/libs/native/windows/x86/bulletjme.dll
  25. BIN
      jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll
  26. 2
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp
  27. 265
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp
  28. 134
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.h
  29. 38
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp
  30. 16
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.h
  31. 155
      jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java
  32. 16
      jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java
  33. 12
      jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java
  34. 6
      jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
  35. 20
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  36. 8
      jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java
  37. 12
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  38. 4
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  39. 140
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  40. 48
      jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java
  41. 5
      jme3-core/src/main/java/com/jme3/asset/ImplHandler.java
  42. 87
      jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java
  43. 6
      jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java
  44. 33
      jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java
  45. 146
      jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java
  46. 1
      jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java
  47. 2
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  48. 43
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  49. 62
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  50. 176
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  51. 117
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java
  52. 204
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  53. 34
      jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java
  54. 306
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  55. 38
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  56. 10
      jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
  57. 162
      jme3-core/src/main/java/com/jme3/input/FlyByCamera.java
  58. 70
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  59. 18
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  60. 11
      jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java
  61. 12
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  62. 13
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  63. 17
      jme3-core/src/main/java/com/jme3/math/Transform.java
  64. 16
      jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java
  65. 7
      jme3-core/src/main/java/com/jme3/post/Filter.java
  66. 21
      jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
  67. 22
      jme3-core/src/main/java/com/jme3/renderer/Camera.java
  68. 52
      jme3-core/src/main/java/com/jme3/renderer/Caps.java
  69. 3
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  70. 2
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java
  71. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
  72. 6
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
  73. 7
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
  74. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
  75. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
  76. 33
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  77. 65
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  78. 20
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
  79. 11
      jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java
  80. 30
      jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
  81. 189
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  82. 1
      jme3-core/src/main/java/com/jme3/scene/CameraNode.java
  83. 3
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  84. 57
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  85. 49
      jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
  86. 5
      jme3-core/src/main/java/com/jme3/scene/debug/Grid.java
  87. 20
      jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java
  88. 417
      jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java
  89. 238
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java
  90. 156
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java
  91. 218
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java
  92. 141
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java
  93. 16
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  94. 22
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java
  95. 5
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java
  96. 4
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java
  97. 4
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java
  98. 18
      jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java
  99. 59
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
  100. 21
      jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -2,6 +2,7 @@
**/.classpath **/.classpath
**/.settings **/.settings
**/.project **/.project
**/out
/.gradle/ /.gradle/
/.nb-gradle/ /.nb-gradle/
/.idea/ /.idea/

@ -1,5 +1,6 @@
language: java language: java
sudo: false sudo: false
dist: precise
branches: branches:
only: only:
@ -18,6 +19,9 @@ matrix:
addons: addons:
ssh_known_hosts: github.com ssh_known_hosts: github.com
hosts:
- travisci
hostname: travisci
apt: apt:
packages: packages:
- gcc-multilib - gcc-multilib

@ -23,7 +23,10 @@ environment:
secure: Ek2lqC2e19qQDRRdlvnYyLFBq3TNj6YwKTAPuJ2VElJsxi9lQg+9ZP+VbP4kbHTx6Zaa++vtmOuxLZL7gdILrEEPa1Jix2BBLBfcxBUxe6w= secure: Ek2lqC2e19qQDRRdlvnYyLFBq3TNj6YwKTAPuJ2VElJsxi9lQg+9ZP+VbP4kbHTx6Zaa++vtmOuxLZL7gdILrEEPa1Jix2BBLBfcxBUxe6w=
install: install:
- cmd: del C:\Users\appveyor\.gradle\caches\modules-2\modules-2.lock - cmd: >-
set GRADLE_LOCK=C:\Users\appveyor\.gradle\caches\modules-2\modules-2.lock
if exist %GRADLE_LOCK% del %GRADLE_LOCK%
build_script: build_script:
- cmd: gradlew.bat -PbuildNativeProjects=true :jme3-bullet-native:assemble - cmd: gradlew.bat -PbuildNativeProjects=true :jme3-bullet-native:assemble
@ -40,6 +43,10 @@ on_success:
- cmd: >- - cmd: >-
openssl aes-256-cbc -K %encrypted_f0a0b284e2e8_key% -iv %encrypted_f0a0b284e2e8_iv% -in private\key.enc -out c:\users\appveyor\.ssh\id_rsa -d openssl aes-256-cbc -K %encrypted_f0a0b284e2e8_key% -iv %encrypted_f0a0b284e2e8_iv% -in private\key.enc -out c:\users\appveyor\.ssh\id_rsa -d
git config --global user.email "appveyor"
git config --global user.name "appveyor"
git checkout -q %APPVEYOR_REPO_BRANCH% git checkout -q %APPVEYOR_REPO_BRANCH%
git add -- jme3-bullet-native/libs/native/windows/ git add -- jme3-bullet-native/libs/native/windows/

@ -15,7 +15,7 @@ apply from: file('version.gradle')
subprojects { subprojects {
if(!project.name.equals('jme3-android-examples')) { if(!project.name.equals('jme3-android-examples')) {
apply from: rootProject.file('common.gradle') apply from: rootProject.file('common.gradle')
if (!['jme3-testdata', 'sdk'].contains(project.name)) { if (!project.name.equals('jme3-testdata')) {
apply from: rootProject.file('bintray.gradle') apply from: rootProject.file('bintray.gradle')
} }
} else { } else {
@ -116,7 +116,7 @@ task mergedSource(type: Copy){
} }
task wrapper(type: Wrapper, description: 'Creates and deploys the Gradle wrapper to the current directory.') { task wrapper(type: Wrapper, description: 'Creates and deploys the Gradle wrapper to the current directory.') {
gradleVersion = '3.2.1' gradleVersion = '4.1'
} }
ext { ext {

@ -16,6 +16,9 @@ repositories {
maven { maven {
url "http://nifty-gui.sourceforge.net/nifty-maven-repo" url "http://nifty-gui.sourceforge.net/nifty-maven-repo"
} }
flatDir {
dirs rootProject.file('lib')
}
} }
dependencies { dependencies {

@ -19,8 +19,8 @@ buildAndroidExamples = false
ndkPath = /opt/android-ndk-r10c ndkPath = /opt/android-ndk-r10c
# Path for downloading native Bullet # Path for downloading native Bullet
bulletUrl = https://github.com/bulletphysics/bullet3/archive/2.83.7.zip bulletUrl = https://github.com/bulletphysics/bullet3/archive/2.86.1.zip
bulletFolder = bullet3-2.83.7 bulletFolder = bullet3-2.86.1
bulletZipFile = bullet3.zip bulletZipFile = bullet3.zip
# POM settings # POM settings

Binary file not shown.

@ -1,6 +1,6 @@
#Fri Nov 25 13:05:50 EST 2016 #Sun Sep 17 22:55:30 EDT 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip

22
gradlew vendored

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env sh
############################################################################## ##############################################################################
## ##
@ -154,11 +154,19 @@ if $cygwin ; then
esac esac
fi fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules # Escape application args
function splitJvmOpts() { save ( ) {
JVM_OPTS=("$@") for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
} }
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS APP_ARGS=$(save "$@")
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

6
gradlew.bat vendored

@ -49,7 +49,6 @@ goto fail
@rem Get command-line arguments, handling Windows variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line

@ -5,5 +5,5 @@ if (!hasProperty('mainClass')) {
dependencies { dependencies {
compile project(':jme3-core') compile project(':jme3-core')
compile project(':jme3-plugins') compile project(':jme3-plugins')
compile files('../lib/android.jar') compileOnly 'android:android'
} }

@ -139,8 +139,10 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O
boolean isJoystick = boolean isJoystick =
((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK);
boolean isUnknown =
(source & android.view.InputDevice.SOURCE_UNKNOWN) == android.view.InputDevice.SOURCE_UNKNOWN;
if (isTouch && touchInput != null) { if (touchInput != null && (isTouch || (isUnknown && this.touchInput.isSimulateKeyboard()))) {
// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", // logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}",
// new Object[]{source, isTouch}); // new Object[]{source, isTouch});
consumed = touchInput.onKey(event); consumed = touchInput.onKey(event);

@ -561,4 +561,9 @@ public class AndroidGL implements GL, GLExt, GLFbo {
public void glBlendEquationSeparate(int colorMode, int alphaMode) { public void glBlendEquationSeparate(int colorMode, int alphaMode) {
GLES20.glBlendEquationSeparate(colorMode, alphaMode); GLES20.glBlendEquationSeparate(colorMode, alphaMode);
} }
@Override
public void glFramebufferTextureLayerEXT(int target, int attachment, int texture, int level, int layer) {
throw new UnsupportedOperationException("OpenGL ES 2 does not support texture arrays");
}
} }

@ -53,9 +53,11 @@ import com.jme3.input.dummy.DummyKeyInput;
import com.jme3.input.dummy.DummyMouseInput; import com.jme3.input.dummy.DummyMouseInput;
import com.jme3.renderer.android.AndroidGL; import com.jme3.renderer.android.AndroidGL;
import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLDebugES;
import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo; import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer; import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.opengl.GLTracer;
import com.jme3.system.*; import com.jme3.system.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
@ -195,8 +197,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
timer = new NanoTimer(); timer = new NanoTimer();
Object gl = new AndroidGL(); Object gl = new AndroidGL();
// gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl); if (settings.getBoolean("GraphicsDebug")) {
// gl = new GLDebugES((GL)gl, (GLExt)gl); gl = new GLDebugES((GL) gl, (GLExt) gl, (GLFbo) gl);
}
if (settings.getBoolean("GraphicsTrace")) {
gl = GLTracer.createGlesTracer(gl, GL.class, GLFbo.class, GLExt.class);
}
renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl); renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl);
renderer.initialize(); renderer.initialize();

@ -1,6 +1,7 @@
package com.jme3.scene.plugins.blender.materials; package com.jme3.scene.plugins.blender.materials;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -164,6 +165,12 @@ public final class MaterialContext implements Savable {
material.setColor("Ambient", new ColorRGBA(ambientFactor, ambientFactor, ambientFactor, 1f)); material.setColor("Ambient", new ColorRGBA(ambientFactor, ambientFactor, ambientFactor, 1f));
} }
// initializing unused "user-defined UV coords" to all available
Map<String, List<Vector2f>> unusedUserDefinedUVCoords = Collections.emptyMap();
if(userDefinedUVCoordinates != null && !userDefinedUVCoordinates.isEmpty()) {
unusedUserDefinedUVCoords = new HashMap<>(userDefinedUVCoordinates);
}
// applying textures // applying textures
int textureIndex = 0; int textureIndex = 0;
if (loadedTextures != null && loadedTextures.size() > 0) { if (loadedTextures != null && loadedTextures.size() > 0) {
@ -175,6 +182,8 @@ public final class MaterialContext implements Savable {
String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture()); this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture());
if(usedUserUVSet == null || unusedUserDefinedUVCoords.containsKey(usedUserUVSet)) {
List<Vector2f> uvs = combinedTexture.getResultUVS(); List<Vector2f> uvs = combinedTexture.getResultUVS();
if(uvs != null && uvs.size() > 0) { if(uvs != null && uvs.size() > 0) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]); VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
@ -182,9 +191,10 @@ public final class MaterialContext implements Savable {
geometry.getMesh().setBuffer(uvCoordsBuffer); geometry.getMesh().setBuffer(uvCoordsBuffer);
}//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file) }//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file)
// Remove used "user-defined UV coords" from the unused collection
if(usedUserUVSet != null) { if(usedUserUVSet != null) {
userDefinedUVCoordinates = new HashMap<>(userDefinedUVCoordinates); unusedUserDefinedUVCoords.remove(usedUserUVSet);
userDefinedUVCoordinates.remove(usedUserUVSet); }
} }
} else { } else {
LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length); LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
@ -192,12 +202,12 @@ public final class MaterialContext implements Savable {
} }
} }
if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { if (unusedUserDefinedUVCoords != null && unusedUserDefinedUVCoords.size() > 0) {
LOGGER.fine("Storing unused, user defined UV coordinates sets."); LOGGER.fine("Storing unused, user defined UV coordinates sets.");
if (userDefinedUVCoordinates.size() > TextureHelper.TEXCOORD_TYPES.length) { if (unusedUserDefinedUVCoords.size() > TextureHelper.TEXCOORD_TYPES.length) {
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length); LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length);
} }
for (Entry<String, List<Vector2f>> entry : userDefinedUVCoordinates.entrySet()) { for (Entry<String, List<Vector2f>> entry : unusedUserDefinedUVCoords.entrySet()) {
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) { if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
List<Vector2f> uvs = entry.getValue(); List<Vector2f> uvs = entry.getValue();
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]); VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);

@ -291,6 +291,8 @@ public class MeshHelper extends AbstractBlenderHelper {
Structure defbase = (Structure) parent.getFieldValue("defbase"); Structure defbase = (Structure) parent.getFieldValue("defbase");
List<String> groupNames = new ArrayList<String>(); List<String> groupNames = new ArrayList<String>();
List<Structure> defs = defbase.evaluateListBase(); List<Structure> defs = defbase.evaluateListBase();
if(!defs.isEmpty()) {
for (Structure def : defs) { for (Structure def : defs) {
groupNames.add(def.getFieldValue("name").toString()); groupNames.add(def.getFieldValue("name").toString());
} }
@ -315,6 +317,7 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
} }
} }
}
return result; return result;
} }

@ -31,12 +31,14 @@
*/ */
package com.jme3.scene.plugins.blender.textures; package com.jme3.scene.plugins.blender.textures;
import com.jme3.asset.AssetManager;
import com.jme3.asset.TextureKey;
import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.plugins.AWTLoader; import com.jme3.texture.plugins.AWTLoader;
import com.jme3.texture.plugins.DDSLoader; import com.jme3.texture.plugins.HDRLoader;
import com.jme3.texture.plugins.TGALoader; import java.util.logging.Level;
import java.io.InputStream;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
@ -47,11 +49,29 @@ import java.util.logging.Logger;
*/ */
/* package */class ImageLoader extends AWTLoader { /* package */class ImageLoader extends AWTLoader {
private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName());
private static final Logger hdrLogger = Logger.getLogger(HDRLoader.class.getName()); // Used to silence HDR Errors
protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader /**
* List of Blender-Supported Texture Extensions (we have to guess them, so
* the AssetLoader can find them. Not good, but better than nothing.
* Source: https://docs.blender.org/manual/en/dev/data_system/files/media/image_formats.html
*/
private static final String[] extensions = new String[]
{ /* Windows Bitmap */".bmp",
/* Iris */ ".sgi", ".rgb", ".bw",
/* PNG */ ".png",
/* JPEG */ ".jpg", ".jpeg",
/* JPEG 2000 */ ".jp2", ".j2c",
/* Targa */".tga",
/* Cineon & DPX */".cin", ".dpx",
/* OpenEXR */ ".exr",
/* Radiance HDR */ ".hdr",
/* TIFF */ ".tif", ".tiff",
/* DDS (Direct X) */ ".dds" };
/** /**
* This method loads the image from the blender file itself. It tries each loader to load the image. * This method loads a image which is packed into the blender file.
* It makes use of all the registered AssetLoaders
* *
* @param inputStream * @param inputStream
* blender input stream * blender input stream
@ -60,76 +80,57 @@ import java.util.logging.Logger;
* @param flipY * @param flipY
* if the image should be flipped (does not work with DirectX image) * if the image should be flipped (does not work with DirectX image)
* @return loaded image or null if it could not be loaded * @return loaded image or null if it could not be loaded
* @deprecated This method has only been left in for API compability.
* Use loadTexture instead
*/ */
public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) { public Image loadImage(AssetManager assetManager, BlenderInputStream inputStream, int startPosition, boolean flipY) {
// loading using AWT loader Texture tex = loadTexture(assetManager, inputStream, startPosition, flipY);
inputStream.setPosition(startPosition);
Image result = this.loadImage(inputStream, ImageType.AWT, flipY);
// loading using TGA loader
if (result == null) {
inputStream.setPosition(startPosition);
result = this.loadImage(inputStream, ImageType.TGA, flipY);
}
// loading using DDS loader
if (result == null) {
inputStream.setPosition(startPosition);
result = this.loadImage(inputStream, ImageType.DDS, flipY);
}
if (result == null) { if (tex == null) {
LOGGER.warning("Image could not be loaded by none of available loaders!"); return null;
} else {
return tex.getImage();
} }
return result;
} }
/** /**
* This method loads an image of a specified type from the given input stream. * This method loads a texture which is packed into the blender file.
* It makes use of all the registered AssetLoaders
* *
* @param inputStream * @param inputStream
* the input stream we read the image from * blender input stream
* @param imageType * @param startPosition
* the type of the image {@link ImageType} * position in the stream where the image data starts
* @param flipY * @param flipY
* if the image should be flipped (does not work with DirectX image) * if the image should be flipped (does not work with DirectX image)
* @return loaded image or null if it could not be loaded * @return loaded texture or null if it could not be loaded
*/ */
public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) { public Texture loadTexture(AssetManager assetManager, BlenderInputStream inputStream, int startPosition, boolean flipY) {
Image result = null; inputStream.setPosition(startPosition);
switch (imageType) { TextureKey tKey;
case AWT: Texture result = null;
try {
result = this.load(inputStream, flipY); hdrLogger.setLevel(Level.SEVERE); // When we bruteforce try HDR on a non hdr file, it prints unreadable chars
} catch (Exception e) {
LOGGER.warning("Unable to load image using AWT loader!"); for (String ext: extensions) {
} tKey = new TextureKey("dummy" + ext, flipY);
break;
case DDS:
try { try {
result = ddsLoader.load(inputStream); result = assetManager.loadAssetFromStream(tKey, inputStream);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warning("Unable to load image using DDS loader!"); continue;
} }
break;
case TGA: if (result != null) {
try { break; // Could locate a possible asset
result = TGALoader.load(inputStream, flipY);
} catch (Exception e) {
LOGGER.warning("Unable to load image using TGA loader!");
} }
break;
default:
throw new IllegalStateException("Unknown image type: " + imageType);
} }
return result;
if (result == null) {
LOGGER.warning("Texture could not be loaded by any of the available loaders!\n"
+ "Since the file has been packed into the blender file, there is no"
+ "way for us to tell you which texture it was.");
} }
/** return result;
* Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files
*
* @author Marcin Roguski (Kaelthas)
*/
private static enum ImageType {
AWT, TGA, DDS;
} }
} }

@ -72,6 +72,7 @@ import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D; import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace; import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.PlaceholderAssets;
/** /**
* A class that is used in texture calculations. * A class that is used in texture calculations.
@ -251,7 +252,11 @@ public class TextureHelper extends AbstractBlenderHelper {
blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());
// Should the texture be flipped? It works for sinbad .. // Should the texture be flipped? It works for sinbad ..
result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); result = new ImageLoader().loadTexture(blenderContext.getAssetManager(), blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true);
if (result == null) {
result = new Texture2D(PlaceholderAssets.getPlaceholderImage(blenderContext.getAssetManager()));
LOGGER.fine("ImageLoader returned null. It probably failed to load the packed texture, using placeholder asset");
}
} }
} }
//} else { //} else {
@ -614,8 +619,15 @@ public class TextureHelper extends AbstractBlenderHelper {
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
boolean negateTexture = (texflag & 0x04) != 0; boolean negateTexture = (texflag & 0x04) != 0;
boolean colorSet = false;
for (int i = 0; i < mappings.length; ++i) { for (int i = 0; i < mappings.length; ++i) {
if ((mappings[i] & mapto.intValue()) != 0) { if ((mappings[i] & mapto.intValue()) != 0) {
if(mappings[i] == MaterialContext.MTEX_COL) {
colorSet = true;
} else if(colorSet && mappings[i] == MaterialContext.MTEX_ALPHA) {
continue;
}
CombinedTexture combinedTexture = new CombinedTexture(mappings[i], !skyTexture); CombinedTexture combinedTexture = new CombinedTexture(mappings[i], !skyTexture);
int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
@ -646,8 +658,16 @@ public class TextureHelper extends AbstractBlenderHelper {
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>(); Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
for (TextureData data : textures) { for (TextureData data : textures) {
Number mapto = (Number) data.mtex.getFieldValue("mapto"); Number mapto = (Number) data.mtex.getFieldValue("mapto");
boolean colorSet = false;
for (int i = 0; i < mappings.length; ++i) { for (int i = 0; i < mappings.length; ++i) {
if ((mappings[i] & mapto.intValue()) != 0) { if ((mappings[i] & mapto.intValue()) != 0) {
if(mappings[i] == MaterialContext.MTEX_COL) {
colorSet = true;
} else if(colorSet && mappings[i] == MaterialContext.MTEX_ALPHA) {
continue;
}
List<TextureData> datas = result.get(mappings[i]); List<TextureData> datas = result.get(mappings[i]);
if (datas == null) { if (datas == null) {
datas = new ArrayList<TextureData>(); datas = new ArrayList<TextureData>();

@ -163,8 +163,18 @@ public class TextureBlenderAWT extends AbstractTextureBlender {
* the blender context * the blender context
*/ */
protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) { protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) {
// We calculate first the importance of the texture (colFactor * texAlphaValue)
float blendFactor = this.blendFactor * pixelColor[3]; float blendFactor = this.blendFactor * pixelColor[3];
float oneMinusFactor = 1.0f - blendFactor, col; // Then, we get the object material factor ((1 - texture importance) * matAlphaValue)
float oneMinusFactor = (1f - blendFactor) * materialColor[3];
// Finally, we can get the final blendFactor, which is 1 - the material factor.
blendFactor = 1f - oneMinusFactor;
// --- Compact method ---
// float blendFactor = this.blendFactor * (1f - ((1f - pixelColor[3]) * materialColor[3]));
// float oneMinusFactor = 1f - blendFactor;
float col;
switch (blendType) { switch (blendType) {
case MTEX_BLEND: case MTEX_BLEND:

@ -209,7 +209,7 @@ JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_
env->ThrowNew(newExc, "The manifoldPoint does not exist."); env->ThrowNew(newExc, "The manifoldPoint does not exist.");
return 0; return 0;
} }
return mp -> m_lateralFrictionInitialized; return (mp -> m_contactPointFlags) & BT_CONTACT_FLAG_LATERAL_FRICTION_INITIALIZED;
} }
/* /*

@ -129,20 +129,100 @@ extern "C" {
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setUpAxis * Method: setUp
* Signature: (JI)V * Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUp
(JNIEnv *env, jobject object, jlong objectId, jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->setUp(vec);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setAngularVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/ */
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setAngularVelocity
(JNIEnv *env, jobject object, jlong objectId, jint value) { (JNIEnv *env, jobject object, jlong objectId, jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId); btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) { if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException"); jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist."); env->ThrowNew(newExc, "The native object does not exist.");
return; return;
} }
character->setUpAxis(value); btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->setAngularVelocity(vec);
} }
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getAngularVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getAngularVelocity
(JNIEnv *env, jobject object, jlong objectId, jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 a_vel = character->getAngularVelocity();
jmeBulletUtil::convert(env, &a_vel, value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setLinearVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setLinearVelocity
(JNIEnv *env, jobject object, jlong objectId, jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->setLinearVelocity(vec);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getLinearVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearVelocity
(JNIEnv *env, jobject object, jlong objectId, jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 l_vel = character->getLinearVelocity();
jmeBulletUtil::convert(env, &l_vel, value);
}
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setFallSpeed * Method: setFallSpeed
@ -178,25 +258,62 @@ extern "C" {
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setGravity * Method: setGravity
* Signature: (JF)V * Signature: (JLcom/jme3/math/Vector3f;)V
*/ */
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity
(JNIEnv *env, jobject object, jlong objectId, jfloat value) { (JNIEnv *env, jobject object, jlong objectId, jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId); btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) { if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException"); jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist."); env->ThrowNew(newExc, "The native object does not exist.");
return; return;
} }
character->setGravity(value);
btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->setGravity(vec);
} }
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getGravity * Method: getGravity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity
(JNIEnv *env, jobject object, jlong objectId,jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 g = character->getGravity();
jmeBulletUtil::convert(env, &g, value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setLinearDamping
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setLinearDamping
(JNIEnv *env, jobject object, jlong objectId,jfloat value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return ;
}
character->setLinearDamping(value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getLinearDamping
* Signature: (J)F * Signature: (J)F
*/ */
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearDamping
(JNIEnv *env, jobject object, jlong objectId) { (JNIEnv *env, jobject object, jlong objectId) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId); btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) { if (character == NULL) {
@ -204,9 +321,78 @@ extern "C" {
env->ThrowNew(newExc, "The native object does not exist."); env->ThrowNew(newExc, "The native object does not exist.");
return 0; return 0;
} }
return character->getGravity(); return character->getLinearDamping();
} }
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setAngularDamping
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setAngularDamping
(JNIEnv *env, jobject object, jlong objectId,jfloat value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
character->setAngularDamping(value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getAngularDamping
* Signature: (J)F
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getAngularDamping
(JNIEnv *env, jobject object, jlong objectId) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return 0;
}
return character->getAngularDamping();
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setStepHeight
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setStepHeight
(JNIEnv *env, jobject object, jlong objectId,jfloat value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
character->setStepHeight(value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getStepHeight
* Signature: (J)F
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getStepHeight
(JNIEnv *env, jobject object, jlong objectId) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return 0;
}
return character->getStepHeight();
}
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setMaxSlope * Method: setMaxSlope
@ -239,6 +425,39 @@ extern "C" {
return character->getMaxSlope(); return character->getMaxSlope();
} }
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setMaxPenetrationDepth
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxPenetrationDepth
(JNIEnv *env, jobject object, jlong objectId, jfloat value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
character->setMaxPenetrationDepth(value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getMaxPenetrationDepth
* Signature: (J)F
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxPenetrationDepth
(JNIEnv *env, jobject object, jlong objectId) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return 0;
}
return character->getMaxPenetrationDepth();
}
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: onGround * Method: onGround
@ -258,17 +477,37 @@ extern "C" {
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: jump * Method: jump
* Signature: (J)V * Signature: (JLcom/jme3/math/Vector3f;)V
*/ */
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump
(JNIEnv *env, jobject object, jlong objectId) { (JNIEnv *env, jobject object, jlong objectId,jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId); btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) { if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException"); jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist."); env->ThrowNew(newExc, "The native object does not exist.");
return; return;
} }
character->jump(); btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->jump(vec);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: applyImpulse
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_applyImpulse
(JNIEnv *env, jobject object, jlong objectId,jobject value) {
btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->applyImpulse(vec);
} }
/* /*

@ -83,11 +83,47 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setWalkDire
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setUpAxis * Method: setUp
* Signature: (JI)V * Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUp
(JNIEnv *, jobject , jlong , jobject );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setAngularVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setAngularVelocity
(JNIEnv *, jobject , jlong , jobject ) ;
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getAngularVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getAngularVelocity
(JNIEnv *, jobject , jlong , jobject );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setLinearVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setLinearVelocity
(JNIEnv *, jobject , jlong , jobject );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getLinearVelocity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/ */
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearVelocity
(JNIEnv *, jobject, jlong, jint); (JNIEnv *, jobject , jlong , jobject );
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
@ -108,19 +144,73 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpee
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setGravity * Method: setGravity
* Signature: (JF)V * Signature: (JLcom/jme3/math/Vector3f;)V
*/ */
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity
(JNIEnv *, jobject, jlong, jfloat); (JNIEnv *, jobject, jlong, jobject);
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getGravity * Method: getGravity
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity
(JNIEnv *, jobject, jlong, jobject);
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setLinearDamping
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setLinearDamping
(JNIEnv *, jobject , jlong ,jfloat );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getLinearDamping
* Signature: (J)F
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearDamping
(JNIEnv *, jobject , jlong );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setAngularDamping
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setAngularDamping
(JNIEnv *, jobject , jlong ,jfloat );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getAngularDamping
* Signature: (J)F
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getAngularDamping
(JNIEnv *, jobject , jlong );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setStepHeight
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setStepHeight
(JNIEnv *, jobject , jlong ,jfloat );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getStepHeight
* Signature: (J)F * Signature: (J)F
*/ */
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getStepHeight
(JNIEnv *, jobject , jlong ); (JNIEnv *, jobject , jlong );
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setMaxSlope * Method: setMaxSlope
@ -137,6 +227,24 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope
(JNIEnv *, jobject, jlong); (JNIEnv *, jobject, jlong);
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setMaxPenetrationDepth
* Signature: (JF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxPenetrationDepth
(JNIEnv *, jobject , jlong , jfloat );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getMaxPenetrationDepth
* Signature: (J)F
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxPenetrationDepth
(JNIEnv *, jobject , jlong );
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: onGround * Method: onGround
@ -148,10 +256,18 @@ JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGroun
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: jump * Method: jump
* Signature: (J)V * Signature: (JLcom/jme3/math/Vector3f;)V
*/ */
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump
(JNIEnv *, jobject, jlong); (JNIEnv *, jobject, jlong, jobject);
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: applyImpulse
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_applyImpulse
(JNIEnv *, jobject , jlong ,jobject );
/* /*
* Class: com_jme3_bullet_objects_PhysicsCharacter * Class: com_jme3_bullet_objects_PhysicsCharacter

@ -151,6 +151,41 @@ extern "C" {
// } // }
} }
/*
* Class: com_jme3_bullet_objects_PhysicsRigidBody
* Method: setInverseInertiaLocal
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setInverseInertiaLocal
(JNIEnv *env, jobject object, jlong bodyId, jobject value) {
btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
if (body == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
body->setInvInertiaDiagLocal(vec);
}
/*
* Class: com_jme3_bullet_objects_PhysicsRigidBody
* Method: getInverseInertiaLocal
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getInverseInertiaLocal
(JNIEnv *env, jobject object, jlong bodyId, jobject value) {
btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
if (body == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
jmeBulletUtil::convert(env, &body->getInvInertiaDiagLocal(), value);
}
/* /*
* Class: com_jme3_bullet_objects_PhysicsRigidBody * Class: com_jme3_bullet_objects_PhysicsRigidBody
* Method: getPhysicsLocation * Method: getPhysicsLocation
@ -217,7 +252,8 @@ extern "C" {
body->setActivationState(DISABLE_DEACTIVATION); body->setActivationState(DISABLE_DEACTIVATION);
} else { } else {
body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT); body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT);
body->setActivationState(ACTIVE_TAG); body->activate(true);
body->forceActivationState(ACTIVE_TAG);
} }
} }

@ -89,6 +89,22 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsR
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation
(JNIEnv *, jobject, jlong, jobject); (JNIEnv *, jobject, jlong, jobject);
/*
* Class: com_jme3_bullet_objects_PhysicsRigidBody
* Method: setInverseInertiaLocal
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setInverseInertiaLocal
(JNIEnv *, jobject, jlong, jobject);
/*
* Class: com_jme3_bullet_objects_PhysicsRigidBody
* Method: getInverseInertiaLocal
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getInverseInertiaLocal
(JNIEnv *, jobject, jlong, jobject);
/* /*
* Class: com_jme3_bullet_objects_PhysicsRigidBody * Class: com_jme3_bullet_objects_PhysicsRigidBody
* Method: getPhysicsRotation * Method: getPhysicsRotation

@ -128,12 +128,64 @@ public class PhysicsCharacter extends PhysicsCollisionObject {
return walkDirection; return walkDirection;
} }
/**
* @deprecated Deprecated in bullet 2.86.1 use setUp(Vector3f) instead
*/
@Deprecated
public void setUpAxis(int axis) { public void setUpAxis(int axis) {
upAxis = axis; if(axis<0) axis=0;
setUpAxis(characterId, axis); else if(axis>2) axis=2;
switch(axis){
case 0:
setUp(Vector3f.UNIT_X);
break;
case 1:
setUp(Vector3f.UNIT_Y);
break;
case 2:
setUp(Vector3f.UNIT_Z);
}
}
public void setUp(Vector3f axis) {
setUp(characterId, axis);
}
private native void setUp(long characterId, Vector3f axis);
public void setAngularVelocity(Vector3f v){
setAngularVelocity(characterId,v);
}
private native void setAngularVelocity(long characterId, Vector3f v);
public Vector3f getAngularVelocity(Vector3f out){
if(out==null)out=new Vector3f();
getAngularVelocity(characterId,out);
return out;
}
private native void getAngularVelocity(long characterId, Vector3f out);
public void setLinearVelocity(Vector3f v){
setLinearVelocity(characterId,v);
} }
private native void setUpAxis(long characterId, int axis); private native void setLinearVelocity(long characterId, Vector3f v);
public Vector3f getLinearVelocity(Vector3f out){
if(out==null)out=new Vector3f();
getLinearVelocity(characterId,out);
return out;
}
private native void getLinearVelocity(long characterId, Vector3f out);
public int getUpAxis() { public int getUpAxis() {
return upAxis; return upAxis;
@ -161,17 +213,95 @@ public class PhysicsCharacter extends PhysicsCollisionObject {
return jumpSpeed; return jumpSpeed;
} }
/**
* @deprecated Deprecated in bullet 2.86.1. Use setGravity(Vector3f) instead.
*/
@Deprecated
public void setGravity(float value) { public void setGravity(float value) {
setGravity(new Vector3f(0,value,0));
}
public void setGravity(Vector3f value) {
setGravity(characterId, value); setGravity(characterId, value);
} }
private native void setGravity(long characterId, float gravity); private native void setGravity(long characterId, Vector3f gravity);
/**
* @deprecated Deprecated in bullet 2.86.1. Use getGravity(Vector3f) instead.
*/
@Deprecated
public float getGravity() { public float getGravity() {
return getGravity(characterId); return getGravity(null).y;
}
public Vector3f getGravity(Vector3f out) {
if(out==null)out=new Vector3f();
getGravity(characterId,out);
return out;
}
private native void getGravity(long characterId,Vector3f out);
public float getLinearDamping(){
return getLinearDamping(characterId);
}
private native float getLinearDamping(long characterId);
public void setLinearDamping(float v ){
setLinearDamping(characterId,v );
}
private native void setLinearDamping(long characterId,float v);
public float getAngularDamping(){
return getAngularDamping(characterId);
}
private native float getAngularDamping(long characterId);
public void setAngularDamping(float v ){
setAngularDamping(characterId,v );
}
private native void setAngularDamping(long characterId,float v);
public float getStepHeight(){
return getStepHeight(characterId);
}
private native float getStepHeight(long characterId);
public void setStepHeight(float v ){
setStepHeight(characterId,v );
}
private native void setStepHeight(long characterId,float v);
public float getMaxPenetrationDepth(){
return getMaxPenetrationDepth(characterId);
}
private native float getMaxPenetrationDepth(long characterId);
public void setMaxPenetrationDepth(float v ){
setMaxPenetrationDepth(characterId,v );
} }
private native float getGravity(long characterId); private native void setMaxPenetrationDepth(long characterId,float v);
public void setMaxSlope(float slopeRadians) { public void setMaxSlope(float slopeRadians) {
setMaxSlope(characterId, slopeRadians); setMaxSlope(characterId, slopeRadians);
@ -191,11 +321,20 @@ public class PhysicsCharacter extends PhysicsCollisionObject {
private native boolean onGround(long characterId); private native boolean onGround(long characterId);
/**
* @deprecated Deprecated in bullet 2.86.1. Use jump(Vector3f) instead.
*/
@Deprecated
public void jump() { public void jump() {
jump(characterId); jump(Vector3f.UNIT_Y);
}
public void jump(Vector3f dir) {
jump(characterId,dir);
} }
private native void jump(long characterId); private native void jump(long characterId,Vector3f v);
@Override @Override
public void setCollisionShape(CollisionShape collisionShape) { public void setCollisionShape(CollisionShape collisionShape) {

@ -186,6 +186,21 @@ public class PhysicsRigidBody extends PhysicsCollisionObject {
return rot; return rot;
} }
public void setInverseInertiaLocal(Vector3f gravity) {
setInverseInertiaLocal(objectId, gravity);
}
private native void setInverseInertiaLocal(long objectId, Vector3f gravity);
public Vector3f getInverseInertiaLocal(Vector3f trans) {
if (trans == null) {
trans = new Vector3f();
}
getInverseInertiaLocal(objectId, trans);
return trans;
}
private native void getInverseInertiaLocal(long objectId, Vector3f vector);
private native void getPhysicsRotation(long objectId, Quaternion rot); private native void getPhysicsRotation(long objectId, Quaternion rot);
/** /**
@ -352,7 +367,6 @@ public class PhysicsRigidBody extends PhysicsCollisionObject {
public void setGravity(Vector3f gravity) { public void setGravity(Vector3f gravity) {
setGravity(objectId, gravity); setGravity(objectId, gravity);
} }
private native void setGravity(long objectId, Vector3f gravity); private native void setGravity(long objectId, Vector3f gravity);
public float getFriction() { public float getFriction() {

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009-2012 jMonkeyEngine * Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -35,6 +35,7 @@ import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape; import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
import com.jme3.math.Matrix3f; import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.Node; import com.jme3.scene.Node;
@ -111,9 +112,16 @@ public class DebugShapeFactory {
public static Mesh getDebugMesh(CollisionShape shape) { public static Mesh getDebugMesh(CollisionShape shape) {
Mesh mesh = new Mesh(); Mesh mesh = new Mesh();
mesh = new Mesh();
DebugMeshCallback callback = new DebugMeshCallback(); DebugMeshCallback callback = new DebugMeshCallback();
/*
* Populate the mesh based on an unscaled shape;
* the shape's scale will be applied later, to the geometry.
*/
Vector3f savedScale = shape.getScale().clone();
shape.setScale(Vector3f.UNIT_XYZ);
getVertices(shape.getObjectId(), callback); getVertices(shape.getObjectId(), callback);
shape.setScale(savedScale);
mesh.setBuffer(Type.Position, 3, callback.getVertices()); mesh.setBuffer(Type.Position, 3, callback.getVertices());
mesh.getFloatBuffer(Type.Position).clear(); mesh.getFloatBuffer(Type.Position).clear();
return mesh; return mesh;

@ -68,7 +68,11 @@ public final class AnimChannel {
private float blendAmount = 1f; private float blendAmount = 1f;
private float blendRate = 0; private float blendRate = 0;
AnimChannel(AnimControl control){ public AnimChannel(){
}
public AnimChannel(AnimControl control){
this.control = control; this.control = control;
} }

@ -93,6 +93,24 @@ public class Animation implements Savable, Cloneable, JmeCloneable {
return length; return length;
} }
/**
* Set the length of the animation
*
* @param length
*/
public void setLength(float length) {
this.length = length;
}
/**
* Sets the name of the animation
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/** /**
* This method sets the current time of the animation. * This method sets the current time of the animation.
* This method behaves differently for every known track type. * This method behaves differently for every known track type.
@ -209,7 +227,7 @@ public class Animation implements Savable, Cloneable, JmeCloneable {
// isn't cloned at all... even though they all implement clone() methods. -pspeed // isn't cloned at all... even though they all implement clone() methods. -pspeed
SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class); SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class);
for( Track track : tracks ) { for( Track track : tracks ) {
if( track instanceof ClonableTrack ) { if (track instanceof JmeCloneable) {
newTracks.add(cloner.clone(track)); newTracks.add(cloner.clone(track));
} else { } else {
// this is the part that seems fishy // this is the part that seems fishy

@ -31,17 +31,15 @@
*/ */
package com.jme3.animation; package com.jme3.animation;
import static com.jme3.animation.LoopMode.Cycle;
import static com.jme3.animation.LoopMode.DontLoop;
import static com.jme3.animation.LoopMode.Loop;
/** /**
* *
* @author Nehon * @author Nehon
*/ */
public class AnimationUtils { public class AnimationUtils {
public AnimationUtils(){
}
/** /**
* Clamps the time according to duration and loopMode * Clamps the time according to duration and loopMode
* @param time * @param time
@ -50,7 +48,7 @@ public class AnimationUtils {
* @return * @return
*/ */
public static float clampWrapTime(float time, float duration, LoopMode loopMode){ public static float clampWrapTime(float time, float duration, LoopMode loopMode){
if (time == 0) { if (time == 0 || duration == 0) {
return 0; // prevent division by 0 errors return 0; // prevent division by 0 errors
} }
switch (loopMode) { switch (loopMode) {

@ -32,15 +32,15 @@
package com.jme3.animation; package com.jme3.animation;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.material.MatParamOverride;
import com.jme3.math.*; import com.jme3.math.*;
import com.jme3.scene.Geometry; import com.jme3.scene.*;
import com.jme3.scene.Mesh; import com.jme3.shader.VarType;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -723,6 +723,8 @@ public final class Bone implements Savable, JmeCloneable {
if (attachNode == null) { if (attachNode == null) {
attachNode = new Node(name + "_attachnode"); attachNode = new Node(name + "_attachnode");
attachNode.setUserData("AttachedBone", this); attachNode.setUserData("AttachedBone", this);
//We don't want the node to have a numBone set by a parent node so we force it to null
attachNode.addMatParamOverride(new MatParamOverride(VarType.Int, "NumberOfBones", null));
} }
return attachNode; return attachNode;

@ -70,7 +70,7 @@ public final class Skeleton implements Savable, JmeCloneable {
public Skeleton(Bone[] boneList) { public Skeleton(Bone[] boneList) {
this.boneList = boneList; this.boneList = boneList;
List<Bone> rootBoneList = new ArrayList<Bone>(); List<Bone> rootBoneList = new ArrayList<>();
for (int i = boneList.length - 1; i >= 0; i--) { for (int i = boneList.length - 1; i >= 0; i--) {
Bone b = boneList[i]; Bone b = boneList[i];
if (b.getParent() == null) { if (b.getParent() == null) {
@ -289,6 +289,7 @@ public final class Skeleton implements Savable, JmeCloneable {
return sb.toString(); return sb.toString();
} }
@Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
InputCapsule input = im.getCapsule(this); InputCapsule input = im.getCapsule(this);
@ -308,6 +309,7 @@ public final class Skeleton implements Savable, JmeCloneable {
} }
} }
@Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule output = ex.getCapsule(this); OutputCapsule output = ex.getCapsule(this);
output.write(rootBones, "rootBones", null); output.write(rootBones, "rootBones", null);

@ -32,28 +32,24 @@
package com.jme3.animation; package com.jme3.animation;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.material.MatParam; import com.jme3.material.MatParamOverride;
import com.jme3.material.Material;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.*;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.*; import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.shader.VarType; import com.jme3.shader.VarType;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.nio.Buffer; import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -108,10 +104,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* Bone offset matrices, recreated each frame * Bone offset matrices, recreated each frame
*/ */
private transient Matrix4f[] offsetMatrices; private transient Matrix4f[] offsetMatrices;
/**
* Material references used for hardware skinning
*/ private MatParamOverride numberOfBonesParam;
private Set<Material> materials = new HashSet<Material>(); private MatParamOverride boneMatricesParam;
/** /**
* Serialization only. Do not use. * Serialization only. Do not use.
@ -120,11 +116,13 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
private void switchToHardware() { private void switchToHardware() {
numberOfBonesParam.setEnabled(true);
boneMatricesParam.setEnabled(true);
// Next full 10 bones (e.g. 30 on 24 bones) // Next full 10 bones (e.g. 30 on 24 bones)
int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10; int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
for (Material m : materials) { numberOfBonesParam.setValue(numBones);
m.setInt("NumberOfBones", numBones);
}
for (Geometry geometry : targets) { for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh(); Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) { if (mesh != null && mesh.isAnimated()) {
@ -134,11 +132,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
private void switchToSoftware() { private void switchToSoftware() {
for (Material m : materials) { numberOfBonesParam.setEnabled(false);
if (m.getParam("NumberOfBones") != null) { boneMatricesParam.setEnabled(false);
m.clearParam("NumberOfBones");
}
}
for (Geometry geometry : targets) { for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh(); Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) { if (mesh != null && mesh.isAnimated()) {
@ -148,18 +144,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
private boolean testHardwareSupported(RenderManager rm) { private boolean testHardwareSupported(RenderManager rm) {
for (Material m : materials) {
// Some of the animated mesh(es) do not support hardware skinning,
// so it is not supported by the model.
if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) {
Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING,
"Not using hardware skinning for {0}, " +
"because material {1} doesn''t support it.",
new Object[]{spatial, m.getMaterialDef().getName()});
//Only 255 bones max supported with hardware skinning
if (skeleton.getBoneCount() > 255) {
return false; return false;
} }
}
switchToHardware(); switchToHardware();
@ -177,6 +166,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* supported by GPU, it shall be enabled, if its not preferred, or not * supported by GPU, it shall be enabled, if its not preferred, or not
* supported by GPU, then it shall be disabled. * supported by GPU, then it shall be disabled.
* *
* @param preferred
* @see #isHardwareSkinningUsed() * @see #isHardwareSkinningUsed()
*/ */
public void setHardwareSkinningPreferred(boolean preferred) { public void setHardwareSkinningPreferred(boolean preferred) {
@ -211,6 +201,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
throw new IllegalArgumentException("skeleton cannot be null"); throw new IllegalArgumentException("skeleton cannot be null");
} }
this.skeleton = skeleton; this.skeleton = skeleton;
this.numberOfBonesParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
this.boneMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
} }
/** /**
@ -221,8 +213,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
Mesh mesh = geometry.getMesh(); Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) { if (mesh != null && mesh.isAnimated()) {
targets.add(geometry); targets.add(geometry);
materials.add(geometry.getMaterial());
} }
} }
private void findTargets(Node node) { private void findTargets(Node node) {
@ -237,8 +229,21 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
@Override @Override
public void setSpatial(Spatial spatial) { public void setSpatial(Spatial spatial) {
Spatial oldSpatial = this.spatial;
super.setSpatial(spatial); super.setSpatial(spatial);
updateTargetsAndMaterials(spatial); updateTargetsAndMaterials(spatial);
if (oldSpatial != null) {
oldSpatial.removeMatParamOverride(numberOfBonesParam);
oldSpatial.removeMatParamOverride(boneMatricesParam);
}
if (spatial != null) {
spatial.removeMatParamOverride(numberOfBonesParam);
spatial.removeMatParamOverride(boneMatricesParam);
spatial.addMatParamOverride(numberOfBonesParam);
spatial.addMatParamOverride(boneMatricesParam);
}
} }
private void controlRenderSoftware() { private void controlRenderSoftware() {
@ -257,28 +262,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
private void controlRenderHardware() { private void controlRenderHardware() {
offsetMatrices = skeleton.computeSkinningMatrices(); offsetMatrices = skeleton.computeSkinningMatrices();
for (Material m : materials) { boneMatricesParam.setValue(offsetMatrices);
MatParam currentParam = m.getParam("BoneMatrices");
if (currentParam != null) {
if (currentParam.getValue() != offsetMatrices) {
// Check to see if other SkeletonControl
// is operating on this material, in that case, user
// is sharing materials between models which is NOT allowed
// when hardware skinning used.
Logger.getLogger(SkeletonControl.class.getName()).log(Level.SEVERE,
"Material instances cannot be shared when hardware skinning is used. " +
"Ensure all models use unique material instances."
);
}
}
m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
}
} }
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
if (!wasMeshUpdated) { if (!wasMeshUpdated) {
@ -295,7 +281,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
if (hwSkinningSupported) { if (hwSkinningSupported) {
hwSkinningEnabled = true; hwSkinningEnabled = true;
Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for " + spatial); Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial);
} else { } else {
switchToSoftware(); switchToSoftware();
} }
@ -419,28 +405,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
// were shared then this will share them. // were shared then this will share them.
this.targets = cloner.clone(targets); this.targets = cloner.clone(targets);
// Not automatic set cloning yet this.numberOfBonesParam = cloner.clone(numberOfBonesParam);
Set<Material> newMaterials = new HashSet<Material>(); this.boneMatricesParam = cloner.clone(boneMatricesParam);
for( Material m : this.materials ) {
Material mClone = cloner.clone(m);
newMaterials.add(mClone);
if( mClone != m ) {
// Material was really cloned so clear the bone matrices in case
// this is hardware skinned. This allows a local version to be
// used and will be reset on the material. Really this just avoids
// the 'safety' check in controlRenderHardware(). Right now material
// doesn't clone itself with the cloner (and doesn't clone its parameters)
// else this would be unnecessary.
MatParam boneMatrices = mClone.getParam("BoneMatrices");
// ...because for some strange reason you can't clear a non-existant
// parameter.
if( boneMatrices != null ) {
mClone.clearParam("BoneMatrices");
}
}
}
this.materials = newMaterials;
} }
/** /**
@ -533,7 +499,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
if (maxWeightsPerVert <= 0) { if (maxWeightsPerVert <= 0) {
throw new IllegalStateException("Max weights per vert is incorrectly set!"); throw new IllegalStateException("Max weights per vert is incorrectly set!");
} }
int fourMinusMaxWeights = 4 - maxWeightsPerVert; int fourMinusMaxWeights = 4 - maxWeightsPerVert;
// NOTE: This code assumes the vertex buffer is in bind pose // NOTE: This code assumes the vertex buffer is in bind pose
@ -547,14 +512,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
fnb.rewind(); fnb.rewind();
// get boneIndexes and weights for mesh // get boneIndexes and weights for mesh
ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
ib.rewind();
wb.rewind(); wb.rewind();
float[] weights = wb.array(); float[] weights = wb.array();
byte[] indices = ib.array();
int idxWeights = 0; int idxWeights = 0;
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
@ -592,7 +555,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
for (int w = maxWeightsPerVert - 1; w >= 0; w--) { for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights]; float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[indices[idxWeights++] & 0xff]; Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
@ -665,14 +628,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
// get boneIndexes and weights for mesh // get boneIndexes and weights for mesh
ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
ib.rewind();
wb.rewind(); wb.rewind();
float[] weights = wb.array(); float[] weights = wb.array();
byte[] indices = ib.array();
int idxWeights = 0; int idxWeights = 0;
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
@ -725,7 +686,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
for (int w = maxWeightsPerVert - 1; w >= 0; w--) { for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights]; float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[indices[idxWeights++] & 0xff]; Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
@ -783,7 +744,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(skeleton, "skeleton", null); oc.write(skeleton, "skeleton", null);
//Targets and materials don't need to be saved, they'll be gathered on each frame
oc.write(numberOfBonesParam, "numberOfBonesParam", null);
oc.write(boneMatricesParam, "boneMatricesParam", null);
} }
@Override @Override
@ -791,6 +754,16 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
super.read(im); super.read(im);
InputCapsule in = im.getCapsule(this); InputCapsule in = im.getCapsule(this);
skeleton = (Skeleton) in.readSavable("skeleton", null); skeleton = (Skeleton) in.readSavable("skeleton", null);
numberOfBonesParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null);
boneMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null);
if (numberOfBonesParam == null) {
numberOfBonesParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
boneMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
getSpatial().addMatParamOverride(numberOfBonesParam);
getSpatial().addMatParamOverride(boneMatricesParam);
}
} }
/** /**
@ -800,7 +773,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
*/ */
private void updateTargetsAndMaterials(Spatial spatial) { private void updateTargetsAndMaterials(Spatial spatial) {
targets.clear(); targets.clear();
materials.clear();
if (spatial instanceof Node) { if (spatial instanceof Node) {
findTargets((Node) spatial); findTargets((Node) spatial);

@ -39,6 +39,9 @@ import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -47,7 +50,7 @@ import java.util.Arrays;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class SpatialTrack implements Track { public class SpatialTrack implements Track, JmeCloneable {
/** /**
* Translations of the track. * Translations of the track.
@ -64,6 +67,12 @@ public class SpatialTrack implements Track {
*/ */
private CompactVector3Array scales; private CompactVector3Array scales;
/**
* The spatial to which this track applies.
* Note that this is optional, if no spatial is defined, the AnimControl's Spatial will be used.
*/
private Spatial trackSpatial;
/** /**
* The times of the animations frames. * The times of the animations frames.
*/ */
@ -97,7 +106,10 @@ public class SpatialTrack implements Track {
* the current time of the animation * the current time of the animation
*/ */
public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
Spatial spatial = control.getSpatial(); Spatial spatial = trackSpatial;
if (spatial == null) {
spatial = control.getSpatial();
}
Vector3f tempV = vars.vect1; Vector3f tempV = vars.vect1;
Vector3f tempS = vars.vect2; Vector3f tempS = vars.vect2;
@ -153,10 +165,12 @@ public class SpatialTrack implements Track {
tempS.interpolateLocal(tempS2, blend); tempS.interpolateLocal(tempS2, blend);
} }
if (translations != null) if (translations != null) {
spatial.setLocalTranslation(tempV); spatial.setLocalTranslation(tempV);
if (rotations != null) }
if (rotations != null) {
spatial.setLocalRotation(tempQ); spatial.setLocalRotation(tempQ);
}
if (scales != null) { if (scales != null) {
spatial.setLocalScale(tempS); spatial.setLocalScale(tempS);
} }
@ -236,17 +250,26 @@ public class SpatialTrack implements Track {
return times == null ? 0 : times[times.length - 1] - times[0]; return times == null ? 0 : times[times.length - 1] - times[0];
} }
@Override
public Track clone() {
return (Track) jmeClone();
}
@Override @Override
public float[] getKeyFrameTimes() { public float[] getKeyFrameTimes() {
return times; return times;
} }
/** public void setTrackSpatial(Spatial trackSpatial) {
* This method creates a clone of the current object. this.trackSpatial = trackSpatial;
* @return a clone of the current object }
*/
public Spatial getTrackSpatial() {
return trackSpatial;
}
@Override @Override
public SpatialTrack clone() { public Object jmeClone() {
int tablesLength = times.length; int tablesLength = times.length;
float[] timesCopy = this.times.clone(); float[] timesCopy = this.times.clone();
@ -258,6 +281,11 @@ public class SpatialTrack implements Track {
return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy); return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);
} }
@Override
public void cloneFields(Cloner cloner, Object original) {
this.trackSpatial = cloner.clone(((SpatialTrack) original).trackSpatial);
}
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
@ -265,6 +293,7 @@ public class SpatialTrack implements Track {
oc.write(rotations, "rotations", null); oc.write(rotations, "rotations", null);
oc.write(times, "times", null); oc.write(times, "times", null);
oc.write(scales, "scales", null); oc.write(scales, "scales", null);
oc.write(trackSpatial, "trackSpatial", null);
} }
@Override @Override
@ -274,5 +303,6 @@ public class SpatialTrack implements Track {
rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
times = ic.readFloatArray("times", null); times = ic.readFloatArray("times", null);
scales = (CompactVector3Array) ic.readSavable("scales", null); scales = (CompactVector3Array) ic.readSavable("scales", null);
trackSpatial = (Spatial) ic.readSavable("trackSpatial", null);
} }
} }

@ -32,6 +32,7 @@
package com.jme3.asset; package com.jme3.asset;
import com.jme3.asset.cache.AssetCache; import com.jme3.asset.cache.AssetCache;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -279,9 +280,9 @@ final class ImplHandler {
// Synchronized access must be used for any ops on classToLoaderMap // Synchronized access must be used for any ops on classToLoaderMap
// Find the loader ImplThreadLocal for this class // Find the loader ImplThreadLocal for this class
synchronized (classToLoaderMap){ synchronized (classToLoaderMap){
ImplThreadLocal local = classToLoaderMap.get(loaderType);
// Remove it from the class->loader map // Remove it from the class->loader map
classToLoaderMap.remove(loaderType); ImplThreadLocal local = classToLoaderMap.remove(loaderType);
if (local == null) return;
// Remove it from the extension->loader map // Remove it from the extension->loader map
for (String extension : local.getExtensions()){ for (String extension : local.getExtensions()){
extensionToLoaderMap.remove(extension); extensionToLoaderMap.remove(extension);

@ -36,14 +36,18 @@ import com.jme3.app.Application;
import com.jme3.app.state.AppState; import com.jme3.app.state.AppState;
import com.jme3.app.state.AppStateManager; import com.jme3.app.state.AppStateManager;
import com.jme3.cinematic.events.AbstractCinematicEvent; import com.jme3.cinematic.events.AbstractCinematicEvent;
import com.jme3.cinematic.events.CameraEvent;
import com.jme3.cinematic.events.CinematicEvent; import com.jme3.cinematic.events.CinematicEvent;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.CameraNode; import com.jme3.scene.CameraNode;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl; import com.jme3.scene.control.CameraControl;
import com.jme3.scene.control.CameraControl.ControlDirection; import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.control.Control;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -88,12 +92,12 @@ import java.util.logging.Logger;
*/ */
public class Cinematic extends AbstractCinematicEvent implements AppState { public class Cinematic extends AbstractCinematicEvent implements AppState {
private static final Logger logger = Logger.getLogger(Application.class.getName()); private static final Logger logger = Logger.getLogger(Cinematic.class.getName());
private Node scene; private Node scene;
protected TimeLine timeLine = new TimeLine(); protected TimeLine timeLine = new TimeLine();
private int lastFetchedKeyFrame = -1; private int lastFetchedKeyFrame = -1;
private List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>(); private List<CinematicEvent> cinematicEvents = new ArrayList<>();
private Map<String, CameraNode> cameras = new HashMap<String, CameraNode>(); private Map<String, CameraNode> cameras = new HashMap<>();
private CameraNode currentCam; private CameraNode currentCam;
private boolean initialized = false; private boolean initialized = false;
private Map<String, Map<Object, Object>> eventsData; private Map<String, Map<Object, Object>> eventsData;
@ -104,6 +108,19 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
* directly * directly
*/ */
public Cinematic() { public Cinematic() {
super();
}
public Cinematic(float initialDuration) {
super(initialDuration);
}
public Cinematic(LoopMode loopMode) {
super(loopMode);
}
public Cinematic(float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode);
} }
/** /**
@ -206,8 +223,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(cinematicEvents.toArray(new CinematicEvent[cinematicEvents.size()]), "cinematicEvents", null);
oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
oc.writeStringSavableMap(cameras, "cameras", null); oc.writeStringSavableMap(cameras, "cameras", null);
oc.write(timeLine, "timeLine", null); oc.write(timeLine, "timeLine", null);
@ -224,7 +240,11 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
cinematicEvents = ic.readSavableArrayList("cinematicEvents", null); Savable[] events = ic.readSavableArray("cinematicEvents", null);
for (Savable c : events) {
// addCinematicEvent(((CinematicEvent) c).getTime(), (CinematicEvent) c)
cinematicEvents.add((CinematicEvent) c);
}
cameras = (Map<String, CameraNode>) ic.readStringSavableMap("cameras", null); cameras = (Map<String, CameraNode>) ic.readStringSavableMap("cameras", null);
timeLine = (TimeLine) ic.readSavable("timeLine", null); timeLine = (TimeLine) ic.readSavable("timeLine", null);
} }
@ -244,7 +264,6 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
ce.setSpeed(speed); ce.setSpeed(speed);
} }
} }
/** /**
@ -258,7 +277,11 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
for (CinematicEvent cinematicEvent : cinematicEvents) { for (CinematicEvent cinematicEvent : cinematicEvents) {
cinematicEvent.initEvent(app, this); cinematicEvent.initEvent(app, this);
} }
if(!cameras.isEmpty()){
for(CameraNode n : cameras.values()){
n.setCamera(app.getCamera());
}
}
initialized = true; initialized = true;
} }
@ -315,8 +338,9 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
* *
* @param tpf * @param tpf
*/ */
@Override
public void update(float tpf) { public void update(float tpf) {
if (isInitialized()) { if (isInitialized() && playState == PlayState.Playing) {
internalUpdate(tpf); internalUpdate(tpf);
} }
} }
@ -338,13 +362,11 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
} }
} }
for (int i = 0; i < cinematicEvents.size(); i++) { for (int i = 0; i < cinematicEvents.size(); i++) {
CinematicEvent ce = cinematicEvents.get(i); CinematicEvent ce = cinematicEvents.get(i);
ce.internalUpdate(tpf); ce.internalUpdate(tpf);
} }
lastFetchedKeyFrame = keyFrameIndex; lastFetchedKeyFrame = keyFrameIndex;
} }
@ -386,8 +408,8 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
* Adds a cinematic event to this cinematic at the given timestamp. This * Adds a cinematic event to this cinematic at the given timestamp. This
* operation returns a keyFrame * operation returns a keyFrame
* *
* @param timeStamp the time when the event will start after the beginning of * @param timeStamp the time when the event will start after the beginning
* the cinematic * of the cinematic
* @param cinematicEvent the cinematic event * @param cinematicEvent the cinematic event
* @return the keyFrame for that event. * @return the keyFrame for that event.
*/ */
@ -580,39 +602,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
* Cinematic#bindCamera()) * Cinematic#bindCamera())
*/ */
public void activateCamera(final float timeStamp, final String cameraName) { public void activateCamera(final float timeStamp, final String cameraName) {
addCinematicEvent(timeStamp, new AbstractCinematicEvent() { addCinematicEvent(timeStamp, new CameraEvent(this, cameraName));
@Override
public void play() {
super.play();
stop();
}
@Override
public void onPlay() {
setActiveCamera(cameraName);
}
@Override
public void onUpdate(float tpf) {
}
@Override
public void onStop() {
}
@Override
public void onPause() {
}
@Override
public void forceStop() {
}
@Override
public void setTime(float time) {
play();
}
});
} }
/** /**
@ -684,6 +674,11 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
*/ */
public void setScene(Node scene) { public void setScene(Node scene) {
this.scene = scene; this.scene = scene;
if(!cameras.isEmpty()){
for(CameraNode n : cameras.values()){
this.scene.attachChild(n);
}
}
} }
/** /**

@ -43,7 +43,11 @@ import java.util.List;
*/ */
public class KeyFrame implements Savable { public class KeyFrame implements Savable {
List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>(); public KeyFrame(){
}
List<CinematicEvent> cinematicEvents = new ArrayList<>();
private int index; private int index;
public List<CinematicEvent> getCinematicEvents() { public List<CinematicEvent> getCinematicEvents() {

@ -41,7 +41,10 @@ import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter; import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter; import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -80,6 +83,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
* constructors * constructors
*/ */
public AnimationEvent() { public AnimationEvent() {
super();
} }
/** /**
@ -90,6 +94,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
*/ */
public AnimationEvent(Spatial model, String animationName) { public AnimationEvent(Spatial model, String animationName) {
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
} }
@ -104,6 +109,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
public AnimationEvent(Spatial model, String animationName, float initialDuration) { public AnimationEvent(Spatial model, String animationName, float initialDuration) {
super(initialDuration); super(initialDuration);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
} }
@ -119,6 +125,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
super(loopMode); super(loopMode);
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
} }
@ -134,6 +141,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode) { public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode); super(initialDuration, loopMode);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
} }
@ -149,6 +157,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
public AnimationEvent(Spatial model, String animationName, float initialDuration, float blendTime) { public AnimationEvent(Spatial model, String animationName, float initialDuration, float blendTime) {
super(initialDuration); super(initialDuration);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.blendTime = blendTime; this.blendTime = blendTime;
} }
@ -167,6 +176,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
super(loopMode); super(loopMode);
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.blendTime = blendTime; this.blendTime = blendTime;
} }
@ -185,6 +195,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, float blendTime) { public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, float blendTime) {
super(initialDuration, loopMode); super(initialDuration, loopMode);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.blendTime = blendTime; this.blendTime = blendTime;
} }
@ -203,6 +214,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
super(loopMode); super(loopMode);
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.channelIndex = channelIndex; this.channelIndex = channelIndex;
} }
@ -217,6 +229,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
*/ */
public AnimationEvent(Spatial model, String animationName, int channelIndex) { public AnimationEvent(Spatial model, String animationName, int channelIndex) {
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
this.channelIndex = channelIndex; this.channelIndex = channelIndex;
@ -233,6 +246,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
*/ */
public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex, float blendTime) { public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex, float blendTime) {
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.loopMode = loopMode; this.loopMode = loopMode;
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
@ -252,6 +266,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
public AnimationEvent(Spatial model, String animationName, float initialDuration, int channelIndex) { public AnimationEvent(Spatial model, String animationName, float initialDuration, int channelIndex) {
super(initialDuration); super(initialDuration);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.channelIndex = channelIndex; this.channelIndex = channelIndex;
} }
@ -270,6 +285,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, int channelIndex) { public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, int channelIndex) {
super(initialDuration, loopMode); super(initialDuration, loopMode);
this.model = model; this.model = model;
this.modelName = model.getName();
this.animationName = animationName; this.animationName = animationName;
this.channelIndex = channelIndex; this.channelIndex = channelIndex;
} }
@ -299,6 +315,18 @@ public class AnimationEvent extends AbstractCinematicEvent {
model = cinematic.getScene().getChild(modelName); model = cinematic.getScene().getChild(modelName);
} }
if (model != null) { if (model != null) {
if(cinematic.getScene() != null){
Spatial sceneModel = cinematic.getScene().getChild(model.getName());
if(sceneModel != null){
Node parent = sceneModel.getParent();
parent.detachChild(sceneModel);
sceneModel = model;
parent.attachChild(sceneModel);
} else {
cinematic.getScene().attachChild(model);
}
}
channel = model.getControl(AnimControl.class).createChannel(); channel = model.getControl(AnimControl.class).createChannel();
map.put(channelIndex, channel); map.put(channelIndex, channel);
} else { } else {
@ -401,6 +429,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(model, "model", null); oc.write(model, "model", null);
oc.write(modelName, "modelName", null);
oc.write(animationName, "animationName", ""); oc.write(animationName, "animationName", "");
oc.write(blendTime, "blendTime", 0f); oc.write(blendTime, "blendTime", 0f);
oc.write(channelIndex, "channelIndex", 0); oc.write(channelIndex, "channelIndex", 0);
@ -411,9 +440,9 @@ public class AnimationEvent extends AbstractCinematicEvent {
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
if (im.getFormatVersion() == 0) { // if (im.getFormatVersion() == 0) {
modelName = ic.readString("modelName", ""); modelName = ic.readString("modelName", "");
} // }
//FIXME always the same issue, because of the clonning of assets, this won't work //FIXME always the same issue, because of the clonning of assets, this won't work
//we have to somehow store userdata in the spatial and then recurse the //we have to somehow store userdata in the spatial and then recurse the
//scene sub scenegraph to find the correct instance of the model //scene sub scenegraph to find the correct instance of the model

@ -0,0 +1,146 @@
/*
* Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic.events;
import com.jme3.app.Application;
import com.jme3.cinematic.Cinematic;
import com.jme3.cinematic.TimeLine;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.scene.CameraNode;
import java.io.IOException;
import java.util.Map;
/**
*
* @author Rickard <neph1 @ github>
*/
public class CameraEvent extends AbstractCinematicEvent{
private String cameraName;
private Cinematic cinematic;
public String getCameraName() {
return cameraName;
}
public void setCameraName(String cameraName) {
this.cameraName = cameraName;
}
public CameraEvent(){
}
public CameraEvent(Cinematic parentEvent, String cameraName){
this.cinematic = parentEvent;
this.cameraName = cameraName;
}
@Override
public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic);
this.cinematic = cinematic;
}
@Override
public void play() {
super.play();
stop();
}
@Override
public void onPlay() {
cinematic.setActiveCamera(cameraName);
}
@Override
public void onUpdate(float tpf) {
}
@Override
public void onStop() {
}
@Override
public void onPause() {
}
@Override
public void forceStop() {
}
@Override
public void setTime(float time) {
play();
}
public Cinematic getCinematic() {
return cinematic;
}
public void setCinematic(Cinematic cinematic) {
this.cinematic = cinematic;
}
/**
* used internally for serialization
*
* @param ex
* @throws IOException
*/
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(cameraName, "cameraName", null);
}
/**
* used internally for serialization
*
* @param im
* @throws IOException
*/
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
cameraName = ic.readString("cameraName", null);
}
}

@ -147,6 +147,7 @@ public class SoundEvent extends AbstractCinematicEvent {
* used for serialization * used for serialization
*/ */
public SoundEvent() { public SoundEvent() {
super();
} }
@Override @Override

@ -1016,7 +1016,7 @@ public class ParticleEmitter extends Geometry {
particles[idx2] = p1; particles[idx2] = p1;
} }
private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){ protected void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){
// applying gravity // applying gravity
p.velocity.x -= gravity.x * tpf; p.velocity.x -= gravity.x * tpf;
p.velocity.y -= gravity.y * tpf; p.velocity.y -= gravity.y * tpf;

@ -48,6 +48,7 @@ import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace; import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.MipMapGenerator;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@ -72,6 +73,8 @@ public class EnvironmentCamera extends BaseAppState {
protected Image.Format imageFormat = Image.Format.RGB16F; protected Image.Format imageFormat = Image.Format.RGB16F;
public TextureCubeMap debugEnv;
//Axis for cameras //Axis for cameras
static { static {
//PositiveX axis(left, up, direction) //PositiveX axis(left, up, direction)
@ -108,7 +111,10 @@ public class EnvironmentCamera extends BaseAppState {
protected Vector3f position = new Vector3f(); protected Vector3f position = new Vector3f();
protected ColorRGBA backGroundColor; protected ColorRGBA backGroundColor;
protected int size = 128; /**
* The size of environment cameras.
*/
protected int size = 256;
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>(); private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
@ -185,19 +191,48 @@ public class EnvironmentCamera extends BaseAppState {
buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8); buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8);
renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat); renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat);
images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear); images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear);
MipMapGenerator.generateMipMaps(images[i]);
} }
final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat); final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat);
debugEnv = map;
job.callback.done(map); job.callback.done(map);
map.getImage().dispose(); map.getImage().dispose();
jobs.remove(0); jobs.remove(0);
} }
/**
* Gets the size of environment cameras.
*
* @return the size of environment cameras.
*/
public int getSize() { public int getSize() {
return size; return size;
} }
/**
* Sets the size of environment cameras and rebuild this state if it was initialized.
*
* @param size the size of environment cameras.
*/
public void setSize(final int size) {
this.size = size;
rebuild();
}
/**
* Rebuild all environment cameras.
*/
protected void rebuild() {
if (!isInitialized()) {
return;
}
cleanup(getApplication());
initialize(getApplication());
}
public Vector3f getPosition() { public Vector3f getPosition() {
return position; return position;
} }
@ -224,8 +259,7 @@ public class EnvironmentCamera extends BaseAppState {
this.backGroundColor = app.getViewPort().getBackgroundColor(); this.backGroundColor = app.getViewPort().getBackgroundColor();
final Camera[] cameras = new Camera[6]; final Camera[] cameras = new Camera[6];
final Texture2D[] textures = new Texture2D[6];
Texture2D[] textures = new Texture2D[6];
viewports = new ViewPort[6]; viewports = new ViewPort[6];
framebuffers = new FrameBuffer[6]; framebuffers = new FrameBuffer[6];
@ -241,6 +275,7 @@ public class EnvironmentCamera extends BaseAppState {
} }
} }
@Override @Override
protected void cleanup(Application app) { protected void cleanup(Application app) {
this.backGroundColor = null; this.backGroundColor = null;

@ -31,16 +31,14 @@
*/ */
package com.jme3.environment; package com.jme3.environment;
import com.jme3.light.LightProbe;
import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator;
import com.jme3.environment.generation.IrradianceMapGenerator;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.environment.generation.*;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
/** /**
@ -109,72 +107,77 @@ public class LightProbeFactory {
* @param envCam the EnvironmentCamera * @param envCam the EnvironmentCamera
* @param scene the Scene * @param scene the Scene
* @param genType Fast or HighQuality. Fast may be ok for many types of environment, but you may need high quality when an environment map has very high lighting values.
* @param listener the listener of the genration progress. * @param listener the listener of the genration progress.
* @return the created LightProbe * @return the created LightProbe
*/ */
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) { public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
final LightProbe probe = new LightProbe(); final LightProbe probe = new LightProbe();
probe.setPosition(envCam.getPosition()); probe.setPosition(envCam.getPosition());
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat())); probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() { envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override @Override
public void done(TextureCubeMap map) { public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener); generatePbrMaps(map, probe, envCam.getApplication(), genType, listener);
} }
}); });
return probe; return probe;
} }
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
return makeProbe(envCam, scene, EnvMapUtils.GenerationType.Fast, listener);
}
/** /**
* Updates a LightProbe with the giver EnvironmentCamera in the given scene. * Updates a LightProbe with the given EnvironmentCamera in the given scene.
* * <p>
* Note that this is an assynchronous process that will run on multiple threads. * Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe. * The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done. * The created lightProbe will only be marked as ready when the rendering process is done.
* * <p>
* The JobProgressListener will be notified of the progress of the generation. * The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}. * Note that you can also use a {@link JobProgressAdapter}.
* *
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
*
* @param probe the Light probe to update * @param probe the Light probe to update
* @param envCam the EnvironmentCamera * @param envCam the EnvironmentCamera
* @param scene the Scene * @param scene the Scene
* @param genType Fast or HighQuality. Fast may be ok for many types of environment, but you may need high quality when an environment map has very high lighting values.
* @param listener the listener of the genration progress. * @param listener the listener of the genration progress.
* @return the created LightProbe * @return the created LightProbe
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
*/ */
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) { public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
envCam.setPosition(probe.getPosition()); envCam.setPosition(probe.getPosition());
probe.setReady(false); probe.setReady(false);
if(probe.getIrradianceMap() != null) { if (probe.getPrefilteredEnvMap() != null) {
probe.getIrradianceMap().getImage().dispose();
probe.getPrefilteredEnvMap().getImage().dispose(); probe.getPrefilteredEnvMap().getImage().dispose();
} }
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat())); probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() { envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override @Override
public void done(TextureCubeMap map) { public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener); generatePbrMaps(map, probe, envCam.getApplication(), genType, listener);
} }
}); });
return probe; return probe;
} }
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
return updateProbe(probe, envCam, scene, EnvMapUtils.GenerationType.Fast, listener);
}
/** /**
* Internally called to generate the maps. * Internally called to generate the maps.
* This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map). * This method will spawn 7 thread (one for the Irradiance spherical harmonics generator, and one for each face of the prefiltered env map).
* Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done. * Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done.
* *
* @param envMap the raw env map rendered by the env camera * @param envMap the raw env map rendered by the env camera
@ -182,21 +185,21 @@ public class LightProbeFactory {
* @param app the Application * @param app the Application
* @param listener a progress listener. (can be null if no progress reporting is needed) * @param listener a progress listener. (can be null if no progress reporting is needed)
*/ */
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) { private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
IrradianceMapGenerator irrMapGenerator; IrradianceSphericalHarmonicsGenerator irrShGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6]; PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7)); final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6)); irrShGenerator = new IrradianceSphericalHarmonicsGenerator(app, new JobListener(listener, jobState, probe, 6));
int size = envMap.getImage().getWidth(); int size = envMap.getImage().getWidth();
irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap()); irrShGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), probe);
jobState.executor.execute(irrMapGenerator); jobState.executor.execute(irrShGenerator);
for (int i = 0; i < pemGenerators.length; i++) { for (int i = 0; i < pemGenerators.length; i++) {
pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i)); pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i));
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap()); pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.None, genType, probe.getPrefilteredEnvMap());
jobState.executor.execute(pemGenerators[i]); jobState.executor.execute(pemGenerators[i]);
} }
} }
@ -280,6 +283,7 @@ public class LightProbeFactory {
jobState.done[index] = true; jobState.done[index] = true;
if (jobState.isDone()) { if (jobState.isDone()) {
probe.setNbMipMaps(probe.getPrefilteredEnvMap().getImage().getMipMapSizes().length);
probe.setReady(true); probe.setReady(true);
if (globalListener != null) { if (globalListener != null) {
globalListener.done(probe); globalListener.done(probe);

@ -1,176 +0,0 @@
/*
* 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.environment.generation;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.texture.TextureCubeMap;
import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
/**
*
* Generates the Irrafiance map for PBR. This job can be lauched from a separate
* thread.
*
* TODO there is a lot of duplicate code here with the EnvMapUtils.
*
* @author Nehon
*/
//TODO there is a lot of duplicate code here with the EnvMapUtils. We should,
//either leverage the code from the util class either remove it and only allow
//parallel generation using this runnable.
public class IrradianceMapGenerator extends RunnableWithProgress {
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
/**
* Creates an Irradiance map generator. The app is needed to enqueue the
* call to the EnvironmentCamera when the generation is done, so that this
* process is thread safe.
*
* @param app the Application
* @param listener
*/
public IrradianceMapGenerator(Application app, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
}
/**
* Fills all the genration parameters
*
* @param sourceMap the source cube map
* @param targetMapSize the size of the generated map (width or height in
* pixel)
* @param fixSeamsMethod the method used to fix seams as described here
* {@link EnvMapUtils.FixSeamsMethod}
*
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod;
this.store = store;
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
try {
Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store);
} catch (Exception e) {
e.printStackTrace();
}
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(6);
return null;
}
});
}
/**
* Generates the Irradiance map (used for image based difuse lighting) from
* Spherical Harmonics coefficients previously computed with
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
*
* @param shCoeffs the SH coeffs
* @param targetMapSize the size of the irradiance map to generate
* @param fixSeamsMethod the method to fix seams
* @param store
* @return The irradiance cube map for the given coefficients
*/
public TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap irrCubeMap = store;
setEnd(6 + 6);
for (int i = 0; i < 6; i++) {
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * store.getImage().getFormat().getBitsPerPixel() / 8);
irrCubeMap.getImage().setData(i, buf);
progress();
}
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
float[] shDir = new float[9];
CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < targetMapSize; y++) {
for (int x = 0; x < targetMapSize; x++) {
EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
EnvMapUtils.evalShBasis(texelVect, shDir);
color.set(0, 0, 0, 0);
for (int i = 0; i < EnvMapUtils.NUM_SH_COEFFICIENT; i++) {
color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
1.0f);
}
//clamping the color because very low value close to zero produce artifacts
color.r = Math.max(0.0001f, color.r);
color.g = Math.max(0.0001f, color.g);
color.b = Math.max(0.0001f, color.b);
envMapWriter.setPixel(x, y, face, color);
}
}
progress();
}
return irrCubeMap;
}
}

@ -0,0 +1,117 @@
/*
* 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.environment.generation;
import com.jme3.app.Application;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.texture.TextureCubeMap;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
/**
* Generates the Irradiance map for PBR. This job can be launched from a separate
* thread.
* <p>
* This is not used as we use spherical harmonics directly now, but we may need this code again at some point
*
* @author Nehon
*/
public class IrradianceSphericalHarmonicsGenerator extends RunnableWithProgress {
private TextureCubeMap sourceMap;
private LightProbe store;
private final Application app;
/**
* Creates an Irradiance map generator. The app is needed to enqueue the
* call to the EnvironmentCamera when the generation is done, so that this
* process is thread safe.
*
* @param app the Application
* @param listener
*/
public IrradianceSphericalHarmonicsGenerator(Application app, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
}
/**
* Fills all the genration parameters
*
* @param sourceMap the source cube map
* {@link EnvMapUtils.FixSeamsMethod}
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, LightProbe store) {
this.sourceMap = sourceMap;
this.store = store;
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
try {
Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
EnvMapUtils.prepareShCoefs(shCoeffs);
store.setShCoeffs(shCoeffs);
} catch (Exception e) {
e.printStackTrace();
}
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(6);
return null;
}
});
}
}

@ -31,30 +31,23 @@
*/ */
package com.jme3.environment.generation; package com.jme3.environment.generation;
import com.jme3.app.Application;
import com.jme3.environment.util.CubeMapWrapper; import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils; import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application; import com.jme3.math.*;
import com.jme3.math.ColorRGBA;
import static com.jme3.math.FastMath.abs;
import static com.jme3.math.FastMath.clamp;
import static com.jme3.math.FastMath.pow;
import static com.jme3.math.FastMath.sqrt;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import static com.jme3.environment.util.EnvMapUtils.getHammersleyPoint;
import static com.jme3.environment.util.EnvMapUtils.getRoughnessFromMip;
import static com.jme3.environment.util.EnvMapUtils.getSampleFromMip;
import static com.jme3.environment.util.EnvMapUtils.getVectorFromCubemapFaceTexCoord;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static com.jme3.environment.util.EnvMapUtils.*;
import static com.jme3.math.FastMath.abs;
import static com.jme3.math.FastMath.sqrt;
/** /**
*
* Generates one face of the prefiltered environnement map for PBR. This job can * Generates one face of the prefiltered environnement map for PBR. This job can
* be lauched from a separate thread. * be lauched from a separate thread.
* * <p>
* TODO there is a lot of duplicate code here with the EnvMapUtils. * TODO there is a lot of duplicate code here with the EnvMapUtils.
* *
* @author Nehon * @author Nehon
@ -68,6 +61,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
private int targetMapSize; private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod; private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private EnvMapUtils.GenerationType genType;
private TextureCubeMap sourceMap; private TextureCubeMap sourceMap;
private TextureCubeMap store; private TextureCubeMap store;
private final Application app; private final Application app;
@ -96,7 +90,6 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
} }
/** /**
* Fills all the genration parameters * Fills all the genration parameters
* *
@ -105,14 +98,14 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* pixel) * pixel)
* @param fixSeamsMethod the method used to fix seams as described here * @param fixSeamsMethod the method used to fix seams as described here
* {@link EnvMapUtils.FixSeamsMethod} * {@link EnvMapUtils.FixSeamsMethod}
*
* @param store The cube map to store the result in. * @param store The cube map to store the result in.
*/ */
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, EnvMapUtils.GenerationType genType, TextureCubeMap store) {
this.sourceMap = sourceMap; this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize; this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod; this.fixSeamsMethod = fixSeamsMethod;
this.store = store; this.store = store;
this.genType = genType;
init(); init();
} }
@ -163,12 +156,16 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* @return The irradiance cube map for the given coefficients * @return The irradiance cube map for the given coefficients
*/ */
private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
try {
TextureCubeMap pem = store; TextureCubeMap pem = store;
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1); int nbMipMap = store.getImage().getMipMapSizes().length;
setEnd(nbMipMap); setEnd(nbMipMap);
if (!sourceEnvMap.getImage().hasMipmaps() || sourceEnvMap.getImage().getMipMapSizes().length < nbMipMap) {
throw new IllegalArgumentException("The input cube map must have at least " + nbMipMap + "mip maps");
}
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap); CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem); CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
@ -176,62 +173,168 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
Vector3f texelVect = new Vector3f(); Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f(); Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA(); ColorRGBA outColor = new ColorRGBA();
int targetMipMapSize = targetMapSize;
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) { for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
float roughness = getRoughnessFromMip(mipLevel, nbMipMap); float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap); int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
for (int y = 0; y < targetMipMapSize; y++) { for (int y = 0; y < targetMipMapSize; y++) {
for (int x = 0; x < targetMipMapSize; x++) { for (int x = 0; x < targetMipMapSize; x++) {
color.set(0, 0, 0); color.set(0, 0, 0);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, EnvMapUtils.FixSeamsMethod.Wrap); getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, fixSeamsMethod);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color); prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, mipLevel, color);
outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1); outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1);
log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y});
targetWrapper.setPixel(x, y, face, mipLevel, outColor); targetWrapper.setPixel(x, y, face, mipLevel, outColor);
} }
} }
targetMipMapSize /= 2;
progress(); progress();
} }
return pem; return pem;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
} }
private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) { private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, int mipLevel, Vector3f store) {
Vector3f prefilteredColor = store; Vector3f prefilteredColor = store;
float totalWeight = 0.0f; float totalWeight = 0.0f;
int nbRotations = 1;
if (genType == GenerationType.HighQuality) {
nbRotations = numSamples == 1 ? 1 : 18;
}
float rad = 2f * FastMath.PI / (float) nbRotations;
// offset rotation to avoid sampling pattern
float gi = (float) (FastMath.abs(N.z + N.x) * 256.0);
float offset = rad * (FastMath.cos((gi * 0.5f) % (2f * FastMath.PI)) * 0.5f + 0.5f);
// a = roughness² and a2 = a² // a = roughness² and a2 = a²
float a2 = roughness * roughness; float a2 = roughness * roughness;
a2 *= a2; a2 *= a2;
a2 *= 10;
//Computing tangent frame
Vector3f upVector = Vector3f.UNIT_X;
if (abs(N.z) < 0.999) {
upVector = Vector3f.UNIT_Y;
}
Vector3f tangentX = tmp1.set(upVector).crossLocal(N).normalizeLocal();
Vector3f tangentY = tmp2.set(N).crossLocal(tangentX);
// https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
// in local space view == normal == 0,0,1
Vector3f V = new Vector3f(0, 0, 1);
Vector3f lWorld = new Vector3f();
for (int i = 0; i < numSamples; i++) { for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi); Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H); H = importanceSampleGGX(Xi, a2, H);
H.normalizeLocal(); H.normalizeLocal();
tmp.set(H); float VoH = H.z;
float NoH = N.dot(tmp); Vector3f L = H.multLocal(VoH * 2f).subtractLocal(V);
float NoL = L.z;
Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
float NoL = clamp(N.dot(L), 0.0f, 1.0f); float computedMipLevel = mipLevel;
if (NoL > 0) { if (mipLevel != 0) {
envMapReader.getPixel(L, c); computedMipLevel = computeMipLevel(roughness, numSamples, this.targetMapSize, VoH);
prefilteredColor.setX(prefilteredColor.x + c.r * NoL); }
prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
prefilteredColor.setZ(prefilteredColor.z + c.b * NoL); toWorld(L, N, tangentX, tangentY, lWorld);
totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor);
totalWeight += NoL;
for (int j = 1; j < nbRotations; j++) {
rotateDirection(offset + j * rad, L, lWorld);
L.set(lWorld);
toWorld(L, N, tangentX, tangentY, lWorld);
totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor);
}
}
if (totalWeight > 0) {
prefilteredColor.divideLocal(totalWeight);
} }
return prefilteredColor;
}
private float samplePixel(CubeMapWrapper envMapReader, Vector3f lWorld, float NoL, float computedMipLevel, Vector3f store) {
if (NoL <= 0) {
return 0;
}
envMapReader.getPixel(lWorld, computedMipLevel, c);
store.setX(store.x + c.r * NoL);
store.setY(store.y + c.g * NoL);
store.setZ(store.z + c.b * NoL);
return NoL;
}
private void toWorld(Vector3f L, Vector3f N, Vector3f tangentX, Vector3f tangentY, Vector3f store) {
store.set(tangentX).multLocal(L.x);
tmp.set(tangentY).multLocal(L.y);
store.addLocal(tmp);
tmp.set(N).multLocal(L.z);
store.addLocal(tmp);
} }
return prefilteredColor.divideLocal(totalWeight); private float computeMipLevel(float roughness, int numSamples, float size, float voH) {
// H[2] is NoH in local space
// adds 1.e-5 to avoid ggx / 0.0
float NoH = voH + 1E-5f;
// Probability Distribution Function
float Pdf = ggx(NoH, roughness) * NoH / (4.0f * voH);
// Solid angle represented by this sample
float omegaS = 1.0f / (numSamples * Pdf);
// Solid angle covered by 1 pixel with 6 faces that are EnvMapSize X EnvMapSize
float omegaP = 4.0f * FastMath.PI / (6.0f * size * size);
// Original paper suggest biasing the mip to improve the results
float mipBias = 1.0f; // I tested that the result is better with bias 1
double maxLod = Math.log(size) / Math.log(2f);
double log2 = Math.log(omegaS / omegaP) / Math.log(2);
return Math.min(Math.max(0.5f * (float) log2 + mipBias, 0.0f), (float) maxLod);
} }
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
private float ggx(float NoH, float alpha) {
// use GGX / Trowbridge-Reitz, same as Disney and Unreal 4
// cf http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p3
float tmp = alpha / (NoH * NoH * (alpha * alpha - 1.0f) + 1.0f);
return tmp * tmp * (1f / FastMath.PI);
}
private Vector3f rotateDirection(float angle, Vector3f l, Vector3f store) {
float s, c, t;
s = FastMath.sin(angle);
c = FastMath.cos(angle);
t = 1.f - c;
store.x = l.x * c + l.y * s;
store.y = -l.x * s + l.y * c;
store.z = l.z * (t + c);
return store;
}
/**
* Computes GGX half vector in local space
*
* @param xi
* @param a2
* @param store
* @return
*/
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f store) {
if (store == null) { if (store == null) {
store = new Vector3f(); store = new Vector3f();
} }
@ -242,22 +345,9 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi) float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi) float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
Vector3f upVector = Vector3f.UNIT_X; store.x = sinThetaCosPhi;
store.y = sinThetaSinPhi;
if (abs(normal.z) < 0.999) { store.z = cosTheta;
upVector = Vector3f.UNIT_Y;
}
Vector3f tangentX = tmp1.set(upVector).crossLocal(normal).normalizeLocal();
Vector3f tangentY = tmp2.set(normal).crossLocal(tangentX);
// Tangent to world space
tangentX.multLocal(sinThetaCosPhi);
tangentY.multLocal(sinThetaSinPhi);
tmp3.set(normal).multLocal(cosTheta);
// Tangent to world space
store.set(tangentX).addLocal(tangentY).addLocal(tmp3);
return store; return store;
} }

@ -31,17 +31,15 @@
*/ */
package com.jme3.environment.util; package com.jme3.environment.util;
import com.jme3.environment.util.EnvMapUtils; import com.jme3.math.*;
import com.jme3.math.ColorRGBA;
import static com.jme3.math.FastMath.pow;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.DefaultImageRaster; import com.jme3.texture.image.DefaultImageRaster;
import com.jme3.texture.image.MipMapImageRaster; import com.jme3.texture.image.MipMapImageRaster;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import static com.jme3.math.FastMath.pow;
/** /**
* Wraps a Cube map and allows to read from or write pixels into it. * Wraps a Cube map and allows to read from or write pixels into it.
* *
@ -57,6 +55,8 @@ public class CubeMapWrapper {
private final Vector2f uvs = new Vector2f(); private final Vector2f uvs = new Vector2f();
private final Image image; private final Image image;
private final ColorRGBA tmpColor = new ColorRGBA();
/** /**
* Creates a CubeMapWrapper for the given cube map * Creates a CubeMapWrapper for the given cube map
* Note that the cube map must be initialized, and the mipmaps sizes should * Note that the cube map must be initialized, and the mipmaps sizes should
@ -105,7 +105,7 @@ public class CubeMapWrapper {
* @param store the color in which to store the pixel color read. * @param store the color in which to store the pixel color read.
* @return the color of the pixel read. * @return the color of the pixel read.
*/ */
public ColorRGBA getPixel(Vector3f vector, int mipLevel, ColorRGBA store) { public ColorRGBA getPixel(Vector3f vector, float mipLevel, ColorRGBA store) {
if (mipMapRaster == null) { if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps"); throw new IllegalArgumentException("This cube map has no mip maps");
} }
@ -113,10 +113,26 @@ public class CubeMapWrapper {
store = new ColorRGBA(); store = new ColorRGBA();
} }
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch); int lowerMipLevel = (int) mipLevel;
int higherMipLevel = (int) FastMath.ceil(mipLevel);
float ratio = mipLevel - lowerMipLevel;
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[lowerMipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face); mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel); mipMapRaster.setMipLevel(lowerMipLevel);
return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store); mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[higherMipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(higherMipLevel);
mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, tmpColor);
store.r = FastMath.interpolateLinear(ratio, store.r, tmpColor.r);
store.g = FastMath.interpolateLinear(ratio, store.g, tmpColor.g);
store.b = FastMath.interpolateLinear(ratio, store.b, tmpColor.b);
store.a = FastMath.interpolateLinear(ratio, store.a, tmpColor.a);
return store;
} }
/** /**

@ -33,25 +33,19 @@ package com.jme3.environment.util;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.ColorRGBA; import com.jme3.math.*;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image; import com.jme3.texture.*;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace; import com.jme3.texture.image.ColorSpace;
import com.jme3.ui.Picture; import com.jme3.ui.Picture;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import static com.jme3.math.FastMath.*; import static com.jme3.math.FastMath.*;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.util.TempVars;
/** /**
* *
@ -62,6 +56,12 @@ import com.jme3.util.TempVars;
*/ */
public class EnvMapUtils { public class EnvMapUtils {
private static final float sqrtPi = sqrt(PI);
private static final float sqrt3Pi = sqrt(3f / PI);
private static final float sqrt5Pi = sqrt(5f / PI);
private static final float sqrt15Pi = sqrt(15f / PI);
public final static int NUM_SH_COEFFICIENT = 9; public final static int NUM_SH_COEFFICIENT = 9;
// See Peter-Pike Sloan paper for these coefficients // See Peter-Pike Sloan paper for these coefficients
//http://www.ppsloan.org/publications/StupidSH36.pdf //http://www.ppsloan.org/publications/StupidSH36.pdf
@ -82,7 +82,12 @@ public class EnvMapUtils {
/** /**
* No seams fix * No seams fix
*/ */
None; None
}
public static enum GenerationType {
Fast,
HighQuality
} }
/** /**
@ -114,17 +119,7 @@ public class EnvMapUtils {
cubeImage.addData(backImg.getData(0)); cubeImage.addData(backImg.getData(0));
cubeImage.addData(frontImg.getData(0)); cubeImage.addData(frontImg.getData(0));
if (leftImg.getEfficentData() != null) { cubeImage.setMipMapSizes(rightImg.getMipMapSizes());
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(rightImg.getEfficentData());
efficientData.add(leftImg.getEfficentData());
efficientData.add(upImg.getEfficentData());
efficientData.add(downImg.getEfficentData());
efficientData.add(backImg.getEfficentData());
efficientData.add(frontImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(0); cubeMap.setAnisotropicFilter(0);
@ -157,12 +152,7 @@ public class EnvMapUtils {
cubeImage.addData(d.duplicate()); cubeImage.addData(d.duplicate());
} }
if (srcImg.getEfficentData() != null) { cubeImage.setMipMapSizes(srcImg.getMipMapSizes());
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(srcImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter()); cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter());
@ -199,7 +189,7 @@ public class EnvMapUtils {
static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) { static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
if (store == null) { if (store == null) {
throw new IllegalArgumentException("the store parameter ust not be null"); throw new IllegalArgumentException("the store parameter must not be null");
} }
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
@ -254,7 +244,7 @@ public class EnvMapUtils {
float v; float v;
if (fixSeamsMethod == FixSeamsMethod.Stretch) { if (fixSeamsMethod == FixSeamsMethod.Stretch) {
/* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp /* Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L77
* transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */ * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f; u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f;
v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f; v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f;
@ -274,7 +264,7 @@ public class EnvMapUtils {
} }
//compute vector depending on the face //compute vector depending on the face
// Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp // Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L101
switch (face) { switch (face) {
case 0: case 0:
store.set(1f, -v, -u); store.set(1f, -v, -u);
@ -387,62 +377,21 @@ public class EnvMapUtils {
return face; return face;
} }
/*
public static void main(String... argv) {
// for (int givenFace = 0; givenFace < 6; givenFace++) {
//
// //int givenFace = 1;
// for (int x = 0; x < 128; x++) {
// for (int y = 0; y < 128; y++) {
// Vector3f v = EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, 128, givenFace, null, FixSeamsMethod.None);
// Vector2f uvs = new Vector2f();
// int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(v, 128, uvs, FixSeamsMethod.None);
//
// if ((int) uvs.x != x || (int) uvs.y != y) {
// System.err.println("error " + uvs + " should be " + x + "," + y + " vect was " + v);
// }
// if (givenFace != face) {
// System.err.println("error face: " + face + " should be " + givenFace);
// }
// }
// }
// }
// System.err.println("done ");
int total = 0;
for (int i = 0; i < 6; i++) {
int size = (int) pow(2, 7 - i);
int samples = EnvMapUtils.getSampleFromMip(i, 6);
int iterations = (samples * size * size);
total += iterations;
float roughness = EnvMapUtils.getRoughnessFromMip(i, 6);
System.err.println("roughness " + i + " : " + roughness + " , map : " + size + " , samples : " + samples + " , iterations : " + iterations);
System.err.println("reverse " + EnvMapUtils.getMipFromRoughness(roughness, 6));
}
System.err.println("total " + total);
System.err.println(128 * 128 * 1024);
System.err.println("test " + EnvMapUtils.getMipFromRoughness(0.9999f, 6));
System.err.println("nb mip = " + (Math.log(128) / Math.log(2) - 1));
}*/
public static int getSampleFromMip(int mipLevel, int miptot) { public static int getSampleFromMip(int mipLevel, int miptot) {
return mipLevel == 0 ? 1 : Math.min(1 << (miptot - 1 + (mipLevel) * 2), 8192); return mipLevel == 0 ? 1 : Math.min(1 << (miptot - 1 + (mipLevel) * 2), 8192);
} }
public static float getRoughnessFromMip(int miplevel, int miptot) {
float mipScale = 1.0f;
float mipOffset = -0.3f;
return pow(2, (miplevel - (miptot - 1) + mipOffset) / mipScale); //see lagarde's paper https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
//linear roughness
public static float getRoughnessFromMip(int miplevel, int miptot) {
float step = 1f / ((float) miptot - 1);
step *= miplevel;
return step * step;
} }
public static float getMipFromRoughness(float roughness, int miptot) { public static float getMipFromRoughness(float roughness, int miptot) {
float mipScale = 1.0f; return FastMath.sqrt(roughness) * (miptot - 1);
float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;
return (float) Math.max(0.0, Lod);
} }
/** /**
@ -482,7 +431,7 @@ public class EnvMapUtils {
float weight; float weight;
if (cubeMap.getImage().getData(0) == null) { if (cubeMap.getImage().getData(0) == null) {
throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU plase use renderer.readFrameBuffer, to create a CPU image"); throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU please use renderer.readFrameBuffer, to create a CPU image");
} }
int width = cubeMap.getImage().getWidth(); int width = cubeMap.getImage().getWidth();
@ -539,12 +488,6 @@ public class EnvMapUtils {
float yV = texelVect.y; float yV = texelVect.y;
float zV = texelVect.z; float zV = texelVect.z;
float pi = PI;
float sqrtPi = sqrt(pi);
float sqrt3Pi = sqrt(3f / pi);
float sqrt5Pi = sqrt(5f / pi);
float sqrt15Pi = sqrt(15f / pi);
float x2 = xV * xV; float x2 = xV * xV;
float y2 = yV * yV; float y2 = yV * yV;
float z2 = zV * zV; float z2 = zV * zV;
@ -558,140 +501,31 @@ public class EnvMapUtils {
shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f; shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
shDir[7] = -(sqrt15Pi * xV * zV) / 2f; shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
shDir[8] = sqrt15Pi * (x2 - y2) / 4f; shDir[8] = sqrt15Pi * (x2 - y2) / 4f;
// shDir[0] = (1f/(2.f*sqrtPi));
//
// shDir[1] = -(sqrt(3f/pi)*yV)/2.f;
// shDir[2] = (sqrt(3/pi)*zV)/2.f;
// shDir[3] = -(sqrt(3/pi)*xV)/2.f;
//
// shDir[4] = (sqrt(15f/pi)*xV*yV)/2.f;
// shDir[5] = -(sqrt(15f/pi)*yV*zV)/2.f;
// shDir[6] = (sqrt(5f/pi)*(-1 + 3f*z2))/4.f;
// shDir[7] = -(sqrt(15f/pi)*xV*zV)/2.f;
// shDir[8] = sqrt(15f/pi)*(x2 - y2)/4.f;
} }
/** public static void prepareShCoefs(Vector3f[] shCoefs) {
* {@link EnvMapUtils#generateIrradianceMap(com.jme3.math.Vector3f[], com.jme3.texture.TextureCubeMap, int, com.jme3.utils.EnvMapUtils.FixSeamsMethod)
* }
*
* @param shCoeffs the spherical harmonics coefficients to use
* @param targetMapSize the size of the target map
* @return the irradiance map.
*/
public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize) {
return generateIrradianceMap(shCoeffs, targetMapSize, FixSeamsMethod.Wrap, null);
}
/** float coef0 = (1f / (2f * sqrtPi));
* Generates the Irradiance map (used for image based difuse lighting) from float coef1 = -sqrt3Pi / 2f;
* Spherical Harmonics coefficients previously computed with float coef2 = -coef1;
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)} float coef3 = coef1;
* Note that the output cube map is in RGBA8 format. float coef4 = sqrt15Pi / 2f;
* float coef5 = -coef4;
* @param shCoeffs the SH coeffs float coef6 = sqrt5Pi / 4f;
* @param targetMapSize the size of the irradiance map to generate float coef7 = coef5;
* @param fixSeamsMethod the method to fix seams float coef8 = sqrt15Pi / 4f;
* @param store
* @return The irradiance cube map for the given coefficients
*/
public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap irrCubeMap = store;
if (irrCubeMap == null) {
irrCubeMap = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
irrCubeMap.setMagFilter(Texture.MagFilter.Bilinear);
irrCubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
irrCubeMap.getImage().setColorSpace(ColorSpace.Linear);
}
for (int i = 0; i < 6; i++) { shCoefs[0].multLocal(coef0).multLocal(shBandFactor[0]);
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * irrCubeMap.getImage().getFormat().getBitsPerPixel()/8); shCoefs[1].multLocal(coef1).multLocal(shBandFactor[1]);
irrCubeMap.getImage().setData(i, buf); shCoefs[2].multLocal(coef2).multLocal(shBandFactor[2]);
shCoefs[3].multLocal(coef3).multLocal(shBandFactor[3]);
shCoefs[4].multLocal(coef4).multLocal(shBandFactor[4]);
shCoefs[5].multLocal(coef5).multLocal(shBandFactor[5]);
shCoefs[6].multLocal(coef6).multLocal(shBandFactor[6]);
shCoefs[7].multLocal(coef7).multLocal(shBandFactor[7]);
shCoefs[8].multLocal(coef8).multLocal(shBandFactor[8]);
} }
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
float[] shDir = new float[9];
CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < targetMapSize; y++) {
for (int x = 0; x < targetMapSize; x++) {
getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
evalShBasis(texelVect, shDir);
color.set(0, 0, 0, 0);
for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
1.0f);
}
//clamping the color because very low value close to zero produce artifacts
color.r = Math.max(0.0001f, color.r);
color.g = Math.max(0.0001f, color.g);
color.b = Math.max(0.0001f, color.b);
envMapWriter.setPixel(x, y, face, color);
}
}
}
return irrCubeMap;
}
/**
* Generates the prefiltered env map (used for image based specular
* lighting) With the GGX/Shlick brdf
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @param sourceEnvMap
* @param targetMapSize the size of the irradiance map to generate
* @param store
* @param fixSeamsMethod the method to fix seams
* @return The irradiance cube map for the given coefficients
*/
public static TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
if (pem == null) {
pem = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear);
pem.getImage().setColorSpace(ColorSpace.Linear);
}
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
System.err.println("mip level " + mipLevel);
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
for (int face = 0; face < 6; face++) {
System.err.println("face " + face);
for (int y = 0; y < targetMipMapSize; y++) {
for (int x = 0; x < targetMipMapSize; x++) {
color.set(0, 0, 0);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, FixSeamsMethod.Wrap);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
outColor.set(color.x, color.y, color.z, 1.0f);
// System.err.println("coords " + x + "," + y);
targetWrapper.setPixel(x, y, face, mipLevel, outColor);
}
}
}
}
return pem;
}
public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) { public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) {
if (store == null) { if (store == null) {
@ -719,43 +553,6 @@ public class EnvMapUtils {
return store; return store;
} }
private static Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
Vector3f prefilteredColor = store;
float totalWeight = 0.0f;
TempVars vars = TempVars.get();
Vector4f Xi = vars.vect4f1;
Vector3f H = vars.vect1;
Vector3f tmp = vars.vect2;
ColorRGBA c = vars.color;
// a = roughness² and a2 = a²
float a2 = roughness * roughness;
a2 *= a2;
a2 *= 10;
for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H, vars);
H.normalizeLocal();
tmp.set(H);
float NoH = N.dot(tmp);
Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
float NoL = clamp(N.dot(L), 0.0f, 1.0f);
if (NoL > 0) {
envMapReader.getPixel(L, c);
prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
totalWeight += NoL;
}
}
vars.release();
return prefilteredColor.divideLocal(totalWeight);
}
public static Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store, TempVars vars) { public static Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store, TempVars vars) {
if (store == null) { if (store == null) {
store = new Vector3f(); store = new Vector3f();
@ -939,9 +736,12 @@ public class EnvMapUtils {
pem.setMagFilter(Texture.MagFilter.Bilinear); pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear); pem.setMinFilter(Texture.MinFilter.Trilinear);
pem.getImage().setColorSpace(ColorSpace.Linear); pem.getImage().setColorSpace(ColorSpace.Linear);
int nbMipMap = (int) (Math.log(size) / Math.log(2) - 1); int nbMipMap = Math.min(6, (int) (Math.log(size) / Math.log(2)));
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem); CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap); targetWrapper.initMipMaps(nbMipMap);
return pem; return pem;
} }
} }

@ -61,26 +61,10 @@ public class LightsDebugState extends BaseAppState {
private Geometry debugGeom; private Geometry debugGeom;
private Geometry debugBounds; private Geometry debugBounds;
private Material debugMaterial; private Material debugMaterial;
private DebugMode debugMode = DebugMode.PrefilteredEnvMap;
private float probeScale = 1.0f; private float probeScale = 1.0f;
private Spatial scene = null; private Spatial scene = null;
private final List<LightProbe> probes = new ArrayList<LightProbe>(); private final List<LightProbe> probes = new ArrayList<LightProbe>();
/**
* Debug mode for light probes
*/
public enum DebugMode {
/**
* Displays the prefiltered env maps on the debug sphere
*/
PrefilteredEnvMap,
/**
* displays the Irradiance map on the debug sphere
*/
IrradianceMap
}
@Override @Override
protected void initialize(Application app) { protected void initialize(Application app) {
debugNode = new Node("Environment debug Node"); debugNode = new Node("Environment debug Node");
@ -114,12 +98,8 @@ public class LightsDebugState extends BaseAppState {
Material m = probeGeom.getMaterial(); Material m = probeGeom.getMaterial();
probeGeom.setLocalScale(probeScale); probeGeom.setLocalScale(probeScale);
if (probe.isReady()) { if (probe.isReady()) {
if (debugMode == DebugMode.IrradianceMap) {
m.setTexture("CubeMap", probe.getIrradianceMap());
} else {
m.setTexture("CubeMap", probe.getPrefilteredEnvMap()); m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
} }
}
n.setLocalTranslation(probe.getPosition()); n.setLocalTranslation(probe.getPosition());
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius()); n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
break; break;
@ -161,24 +141,6 @@ public class LightsDebugState extends BaseAppState {
rm.renderScene(debugNode, getApplication().getViewPort()); rm.renderScene(debugNode, getApplication().getViewPort());
} }
/**
*
* @see DebugMode
* @return the debug mode
*/
public DebugMode getDebugMode() {
return debugMode;
}
/**
* sets the debug mode
* @see DebugMode
* @param debugMode the debug mode
*/
public void setDebugMode(DebugMode debugMode) {
this.debugMode = debugMode;
}
/** /**
* returns the scale of the probe's debug sphere * returns the scale of the probe's debug sphere

@ -34,6 +34,7 @@ package com.jme3.export;
import com.jme3.animation.Animation; import com.jme3.animation.Animation;
import com.jme3.effect.shapes.*; import com.jme3.effect.shapes.*;
import com.jme3.material.MatParamTexture; import com.jme3.material.MatParamTexture;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@ -191,12 +192,17 @@ public class SavableClassUtil {
String newClassName = remapClass(className); String newClassName = remapClass(className);
synchronized (loaders) { synchronized (loaders) {
for (ClassLoader classLoader : loaders) { for (ClassLoader classLoader : loaders) {
final Class<?> loadedClass;
try {
loadedClass = classLoader.loadClass(newClassName);
} catch (final ClassNotFoundException e) {
continue;
}
try { try {
return (Savable) classLoader.loadClass(newClassName).newInstance(); return (Savable) loadedClass.newInstance();
} catch (InstantiationException e) { } catch (InstantiationException e) {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
} }
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009-2012 jMonkeyEngine * Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -32,7 +32,11 @@
package com.jme3.input; package com.jme3.input;
import com.jme3.collision.MotionAllowedListener; import com.jme3.collision.MotionAllowedListener;
import com.jme3.input.controls.*; import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f; import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
@ -40,12 +44,13 @@ import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
/** /**
* A first person view camera controller. * A first-person camera controller.
* After creation, you must register the camera controller with the *
* dispatcher using #registerWithDispatcher(). * After creation, you (or FlyCamAppState) must register the controller using
* {@link #registerWithInput(com.jme3.input.InputManager)}.
* *
* Controls: * Controls:
* - Move the mouse to rotate the camera * - Move (or, in drag-to-rotate mode, drag) the mouse to rotate the camera
* - Mouse wheel for zooming in or out * - Mouse wheel for zooming in or out
* - WASD keys for moving forward/backward and strafing * - WASD keys for moving forward/backward and strafing
* - QZ keys raise or lower the camera * - QZ keys raise or lower the camera
@ -72,22 +77,43 @@ public class FlyByCamera implements AnalogListener, ActionListener {
CameraInput.FLYCAM_INVERTY CameraInput.FLYCAM_INVERTY
}; };
/**
* camera controlled by this controller (not null)
*/
protected Camera cam; protected Camera cam;
/**
* normalized "up" direction (a unit vector)
*/
protected Vector3f initialUpVec; protected Vector3f initialUpVec;
/**
* rotation-rate multiplier (1=default)
*/
protected float rotationSpeed = 1f; protected float rotationSpeed = 1f;
/**
* translation speed (in world units per second)
*/
protected float moveSpeed = 3f; protected float moveSpeed = 3f;
/**
* zoom-rate multiplier (1=default)
*/
protected float zoomSpeed = 1f; protected float zoomSpeed = 1f;
protected MotionAllowedListener motionAllowed = null; protected MotionAllowedListener motionAllowed = null;
/**
* enable flag for controller (false&rarr;ignoring input)
*/
protected boolean enabled = true; protected boolean enabled = true;
/**
* drag-to-rotate mode flag
*/
protected boolean dragToRotate = false; protected boolean dragToRotate = false;
protected boolean canRotate = false; protected boolean canRotate = false;
protected boolean invertY = false; protected boolean invertY = false;
protected InputManager inputManager; protected InputManager inputManager;
/** /**
* Creates a new FlyByCamera to control the given Camera object. * Creates a new FlyByCamera to control the specified camera.
* @param cam *
* @param cam camera to be controlled (not null)
*/ */
public FlyByCamera(Camera cam){ public FlyByCamera(Camera cam){
this.cam = cam; this.cam = cam;
@ -96,6 +122,7 @@ public class FlyByCamera implements AnalogListener, ActionListener {
/** /**
* Sets the up vector that should be used for the camera. * Sets the up vector that should be used for the camera.
*
* @param upVec * @param upVec
*/ */
public void setUpVector(Vector3f upVec) { public void setUpVector(Vector3f upVec) {
@ -107,56 +134,68 @@ public class FlyByCamera implements AnalogListener, ActionListener {
} }
/** /**
* Sets the move speed. The speed is given in world units per second. * Set the translation speed.
* @param moveSpeed *
* @param moveSpeed new speed (in world units per second)
*/ */
public void setMoveSpeed(float moveSpeed){ public void setMoveSpeed(float moveSpeed){
this.moveSpeed = moveSpeed; this.moveSpeed = moveSpeed;
} }
/** /**
* Gets the move speed. The speed is given in world units per second. * Read the translation speed.
* @return moveSpeed *
* @return current speed (in world units per second)
*/ */
public float getMoveSpeed(){ public float getMoveSpeed(){
return moveSpeed; return moveSpeed;
} }
/** /**
* Sets the rotation speed. * Set the rotation-rate multiplier. The bigger the multiplier, the more
* @param rotationSpeed * rotation for a given movement of the mouse.
*
* @param rotationSpeed new rate multiplier (1=default)
*/ */
public void setRotationSpeed(float rotationSpeed){ public void setRotationSpeed(float rotationSpeed){
this.rotationSpeed = rotationSpeed; this.rotationSpeed = rotationSpeed;
} }
/** /**
* Gets the move speed. The speed is given in world units per second. * Read the rotation-rate multiplier. The bigger the multiplier, the more
* @return rotationSpeed * rotation for a given movement of the mouse.
*
* @return current rate multiplier (1=default)
*/ */
public float getRotationSpeed(){ public float getRotationSpeed(){
return rotationSpeed; return rotationSpeed;
} }
/** /**
* Sets the zoom speed. * Set the zoom-rate multiplier. The bigger the multiplier, the more zoom
* @param zoomSpeed * for a given movement of the mouse wheel.
*
* @param zoomSpeed new rate multiplier (1=default)
*/ */
public void setZoomSpeed(float zoomSpeed) { public void setZoomSpeed(float zoomSpeed) {
this.zoomSpeed = zoomSpeed; this.zoomSpeed = zoomSpeed;
} }
/** /**
* Gets the zoom speed. The speed is a multiplier to increase/decrease * Read the zoom-rate multiplier. The bigger the multiplier, the more zoom
* the zoom rate. * for a given movement of the mouse wheel.
* @return zoomSpeed *
* @return current rate multiplier (1=default)
*/ */
public float getZoomSpeed() { public float getZoomSpeed() {
return zoomSpeed; return zoomSpeed;
} }
/** /**
* @param enable If false, the camera will ignore input. * Enable or disable this controller. When disabled, the controller ignored
* input.
*
* @param enable true to enable, false to disable
*/ */
public void setEnabled(boolean enable){ public void setEnabled(boolean enable){
if (enabled && !enable){ if (enabled && !enable){
@ -168,32 +207,36 @@ public class FlyByCamera implements AnalogListener, ActionListener {
} }
/** /**
* @return If enabled * Test whether this controller is enabled.
* @see FlyByCamera#setEnabled(boolean) *
* @return true if enabled, otherwise false
* @see #setEnabled(boolean)
*/ */
public boolean isEnabled(){ public boolean isEnabled(){
return enabled; return enabled;
} }
/** /**
* Test whether drag-to-rotate mode is enabled.
*
* @return If drag to rotate feature is enabled. * @return If drag to rotate feature is enabled.
* *
* @see FlyByCamera#setDragToRotate(boolean) * @see #setDragToRotate(boolean)
*/ */
public boolean isDragToRotate() { public boolean isDragToRotate() {
return dragToRotate; return dragToRotate;
} }
/** /**
* Set if drag to rotate mode is enabled. * Enable or disable drag-to-rotate mode.
* *
* When true, the user must hold the mouse button * When drag-to-rotate mode is enabled, the user must hold the mouse button
* and drag over the screen to rotate the camera, and the cursor is * and drag over the screen to rotate the camera, and the cursor is visible
* visible until dragged. Otherwise, the cursor is invisible at all times * until dragged. When drag-to-rotate mode is disabled, the cursor is
* and holding the mouse button is not needed to rotate the camera. * invisible at all times and holding the mouse button is not needed to
* This feature is disabled by default. * rotate the camera. This mode is disabled by default.
* *
* @param dragToRotate True if drag to rotate mode is enabled. * @param dragToRotate true to enable, false to disable
*/ */
public void setDragToRotate(boolean dragToRotate) { public void setDragToRotate(boolean dragToRotate) {
this.dragToRotate = dragToRotate; this.dragToRotate = dragToRotate;
@ -203,8 +246,9 @@ public class FlyByCamera implements AnalogListener, ActionListener {
} }
/** /**
* Registers the FlyByCamera to receive input events from the provided * Register this controller to receive input events from the specified input
* Dispatcher. * manager.
*
* @param inputManager * @param inputManager
*/ */
public void registerWithInput(InputManager inputManager){ public void registerWithInput(InputManager inputManager){
@ -277,10 +321,9 @@ public class FlyByCamera implements AnalogListener, ActionListener {
} }
/** /**
* Unregisters the FlyByCamera from the event Dispatcher. * Unregister this controller from its input manager.
*/ */
public void unregisterInput(){ public void unregisterInput(){
if (inputManager == null) { if (inputManager == null) {
return; return;
} }
@ -296,12 +339,16 @@ public class FlyByCamera implements AnalogListener, ActionListener {
Joystick[] joysticks = inputManager.getJoysticks(); Joystick[] joysticks = inputManager.getJoysticks();
if (joysticks != null && joysticks.length > 0){ if (joysticks != null && joysticks.length > 0){
Joystick joystick = joysticks[0]; // No way to unassign axis
// No way to unassing axis
} }
} }
/**
* Rotate the camera by the specified amount around the specified axis.
*
* @param value rotation amount
* @param axis direction of rotation (a unit vector)
*/
protected void rotateCamera(float value, Vector3f axis){ protected void rotateCamera(float value, Vector3f axis){
if (dragToRotate){ if (dragToRotate){
if (canRotate){ if (canRotate){
@ -329,6 +376,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
cam.setAxes(q); cam.setAxes(q);
} }
/**
* Zoom the camera by the specified amount.
*
* @param value zoom amount
*/
protected void zoomCamera(float value){ protected void zoomCamera(float value){
// derive fovY value // derive fovY value
float h = cam.getFrustumTop(); float h = cam.getFrustumTop();
@ -354,6 +406,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
cam.setFrustumRight(w); cam.setFrustumRight(w);
} }
/**
* Translate the camera upward by the specified amount.
*
* @param value translation amount
*/
protected void riseCamera(float value){ protected void riseCamera(float value){
Vector3f vel = new Vector3f(0, value * moveSpeed, 0); Vector3f vel = new Vector3f(0, value * moveSpeed, 0);
Vector3f pos = cam.getLocation().clone(); Vector3f pos = cam.getLocation().clone();
@ -366,6 +423,12 @@ public class FlyByCamera implements AnalogListener, ActionListener {
cam.setLocation(pos); cam.setLocation(pos);
} }
/**
* Translate the camera left or forward by the specified amount.
*
* @param value translation amount
* @param sideways true&rarr;left, false&rarr;forward
*/
protected void moveCamera(float value, boolean sideways){ protected void moveCamera(float value, boolean sideways){
Vector3f vel = new Vector3f(); Vector3f vel = new Vector3f();
Vector3f pos = cam.getLocation().clone(); Vector3f pos = cam.getLocation().clone();
@ -385,6 +448,14 @@ public class FlyByCamera implements AnalogListener, ActionListener {
cam.setLocation(pos); cam.setLocation(pos);
} }
/**
* Callback to notify this controller of an analog input event.
*
* @param name name of the input event
* @param value value of the axis (from 0 to 1)
* @param tpf time per frame (in seconds)
*/
@Override
public void onAnalog(String name, float value, float tpf) { public void onAnalog(String name, float value, float tpf) {
if (!enabled) if (!enabled)
return; return;
@ -416,6 +487,14 @@ public class FlyByCamera implements AnalogListener, ActionListener {
} }
} }
/**
* Callback to notify this controller of an action input event.
*
* @param name name of the input event
* @param value true if the action is "pressed", false otherwise
* @param tpf time per frame (in seconds)
*/
@Override
public void onAction(String name, boolean value, float tpf) { public void onAction(String name, boolean value, float tpf) {
if (!enabled) if (!enabled)
return; return;
@ -424,11 +503,10 @@ public class FlyByCamera implements AnalogListener, ActionListener {
canRotate = value; canRotate = value;
inputManager.setCursorVisible(!value); inputManager.setCursorVisible(!value);
} else if (name.equals(CameraInput.FLYCAM_INVERTY)) { } else if (name.equals(CameraInput.FLYCAM_INVERTY)) {
// Toggle on the up. // Invert the "up" direction.
if( !value ) { if( !value ) {
invertY = !invertY; invertY = !invertY;
} }
} }
} }
} }

@ -50,17 +50,19 @@ import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting. * A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting.
* This is used for indirect lighting in the Physically Based Rendering pipeline. * This is used for indirect lighting in the Physically Based Rendering pipeline.
* *
* A light probe has a position in world space. This is the position from where the Environment Map are rendered. * A light probe has a position in world space. This is the position from where the Environment Map are rendered.
* There are two environment maps held by the LightProbe : * There are two environment data structure held by the LightProbe :
* - The irradiance map (used for indirect diffuse lighting in the PBR pipeline). * - The irradiance spherical harmonics factors (used for indirect diffuse lighting in the PBR pipeline).
* - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline). * - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline).
* Note that when instanciating the LightProbe, both those maps are null. * Note that when instantiating the LightProbe, both of those structures are null.
* To render them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} * To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* and {@link EnvironmentCamera}. * and {@link EnvironmentCamera}.
* *
* The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported). * The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported).
@ -75,12 +77,15 @@ import java.io.IOException;
*/ */
public class LightProbe extends Light implements Savable { public class LightProbe extends Light implements Savable {
private TextureCubeMap irradianceMap; private static final Logger logger = Logger.getLogger(LightProbe.class.getName());
private Vector3f[] shCoeffs;
private TextureCubeMap prefilteredEnvMap; private TextureCubeMap prefilteredEnvMap;
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO); private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
private boolean ready = false; private boolean ready = false;
private Vector3f position = new Vector3f(); private Vector3f position = new Vector3f();
private Node debugNode; private Node debugNode;
private int nbMipMaps;
/** /**
* Empty constructor used for serialization. * Empty constructor used for serialization.
@ -89,23 +94,6 @@ public class LightProbe extends Light implements Savable {
public LightProbe() { public LightProbe() {
} }
/**
* returns the irradiance map texture of this Light probe.
* Note that this Texture may not have image data yet if the LightProbe is not ready
* @return the irradiance map
*/
public TextureCubeMap getIrradianceMap() {
return irradianceMap;
}
/**
* Sets the irradiance map
* @param irradianceMap the irradiance map
*/
public void setIrradianceMap(TextureCubeMap irradianceMap) {
this.irradianceMap = irradianceMap;
}
/** /**
* returns the prefiltered environment map texture of this light probe * returns the prefiltered environment map texture of this light probe
* Note that this Texture may not have image data yet if the LightProbe is not ready * Note that this Texture may not have image data yet if the LightProbe is not ready
@ -127,22 +115,35 @@ public class LightProbe extends Light implements Savable {
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(irradianceMap, "irradianceMap", null); oc.write(shCoeffs, "shCoeffs", null);
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null); oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
oc.write(position, "position", null); oc.write(position, "position", null);
oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
oc.write(ready, "ready", false); oc.write(ready, "ready", false);
oc.write(nbMipMaps, "nbMipMaps", 0);
} }
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null); prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
position = (Vector3f) ic.readSavable("position", this); position = (Vector3f) ic.readSavable("position", null);
bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
nbMipMaps = ic.readInt("nbMipMaps", 0);
ready = ic.readBoolean("ready", false); ready = ic.readBoolean("ready", false);
Savable[] coeffs = ic.readSavableArray("shCoeffs", null);
if (coeffs == null) {
ready = false;
logger.log(Level.WARNING, "LightProbe is missing parameters, it should be recomputed. Please use lightProbeFactory.updateProbe()");
} else {
shCoeffs = new Vector3f[coeffs.length];
for (int i = 0; i < coeffs.length; i++) {
shCoeffs[i] = (Vector3f) coeffs[i];
}
}
} }
/** /**
@ -199,9 +200,6 @@ public class LightProbe extends Light implements Savable {
if (debugNode == null) { if (debugNode == null) {
debugNode = new Node("debug gui probe"); debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager); Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager);
debugNode.attachChild(debugIrrCm);
debugNode.attachChild(debugPfemCm); debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0); debugPfemCm.setLocalTranslation(520, 0, 0);
} }
@ -209,6 +207,14 @@ public class LightProbe extends Light implements Savable {
return debugNode; return debugNode;
} }
public Vector3f[] getShCoeffs() {
return shCoeffs;
}
public void setShCoeffs(Vector3f[] shCoeffs) {
this.shCoeffs = shCoeffs;
}
/** /**
* Returns the position of the LightProbe in world space * Returns the position of the LightProbe in world space
* @return the wolrd space position * @return the wolrd space position
@ -226,6 +232,14 @@ public class LightProbe extends Light implements Savable {
getBounds().setCenter(position); getBounds().setCenter(position);
} }
public int getNbMipMaps() {
return nbMipMaps;
}
public void setNbMipMaps(int nbMipMaps) {
this.nbMipMaps = nbMipMaps;
}
@Override @Override
public boolean intersectsBox(BoundingBox box, TempVars vars) { public boolean intersectsBox(BoundingBox box, TempVars vars) {
return getBounds().intersectsBoundingBox(box); return getBounds().intersectsBoundingBox(box);

@ -37,6 +37,7 @@ import com.jme3.math.*;
import com.jme3.shader.VarType; import com.jme3.shader.VarType;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture.WrapMode;
import java.io.IOException; import java.io.IOException;
/** /**
@ -301,18 +302,19 @@ When arrays can be inserted in J3M files
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(type, "varType", null); oc.write(type, "varType", null);
oc.write(name, "name", null); oc.write(name, "name", null);
if (value == null) {
return;
}
if (value instanceof Savable) { if (value instanceof Savable) {
Savable s = (Savable) value; oc.write((Savable) value, "value_savable", null);
oc.write(s, "value_savable", null);
} else if (value instanceof Float) { } else if (value instanceof Float) {
Float f = (Float) value; oc.write((Float) value, "value_float", 0f);
oc.write(f.floatValue(), "value_float", 0f);
} else if (value instanceof Integer) { } else if (value instanceof Integer) {
Integer i = (Integer) value; oc.write((Integer) value, "value_int", 0);
oc.write(i.intValue(), "value_int", 0);
} else if (value instanceof Boolean) { } else if (value instanceof Boolean) {
Boolean b = (Boolean) value; oc.write((Boolean) value, "value_bool", false);
oc.write(b.booleanValue(), "value_bool", false);
} else if (value.getClass().isArray() && value instanceof Savable[]) { } else if (value.getClass().isArray() && value instanceof Savable[]) {
oc.write((Savable[]) value, "value_savable_array", null); oc.write((Savable[]) value, "value_savable_array", null);
} }

@ -190,7 +190,13 @@ public class ShaderGenerationInfo implements Savable, Cloneable {
@Override @Override
protected ShaderGenerationInfo clone() throws CloneNotSupportedException { protected ShaderGenerationInfo clone() throws CloneNotSupportedException {
ShaderGenerationInfo clone = (ShaderGenerationInfo) super.clone(); final ShaderGenerationInfo clone = (ShaderGenerationInfo) super.clone();
clone.attributes = new ArrayList<>();
clone.vertexUniforms = new ArrayList<>();
clone.fragmentUniforms = new ArrayList<>();
clone.fragmentGlobals = new ArrayList<>();
clone.unusedNodes = new ArrayList<>();
clone.varyings = new ArrayList<>();
for (ShaderNodeVariable attribute : attributes) { for (ShaderNodeVariable attribute : attributes) {
clone.attributes.add(attribute.clone()); clone.attributes.add(attribute.clone());
@ -200,8 +206,9 @@ public class ShaderGenerationInfo implements Savable, Cloneable {
clone.vertexUniforms.add(uniform.clone()); clone.vertexUniforms.add(uniform.clone());
} }
if (vertexGlobal != null) {
clone.vertexGlobal = vertexGlobal.clone(); clone.vertexGlobal = vertexGlobal.clone();
}
for (ShaderNodeVariable varying : varyings) { for (ShaderNodeVariable varying : varyings) {
clone.varyings.add(varying.clone()); clone.varyings.add(varying.clone());

@ -188,6 +188,7 @@ public class TechniqueDef implements Savable, Cloneable {
defineTypes = new ArrayList<VarType>(); defineTypes = new ArrayList<VarType>();
paramToDefineId = new HashMap<String, Integer>(); paramToDefineId = new HashMap<String, Integer>();
definesToShaderMap = new HashMap<DefineList, Shader>(); definesToShaderMap = new HashMap<DefineList, Shader>();
worldBinds = new ArrayList<>();
} }
/** /**
@ -513,11 +514,9 @@ public class TechniqueDef implements Savable, Cloneable {
} }
} }
if (getWorldBindings() != null) { for (final UniformBinding binding : getWorldBindings()) {
for (UniformBinding binding : getWorldBindings()) {
shader.addUniformBinding(binding); shader.addUniformBinding(binding);
} }
}
return shader; return shader;
} }
@ -625,10 +624,6 @@ public class TechniqueDef implements Savable, Cloneable {
* to the list of world parameters, false otherwise. * to the list of world parameters, false otherwise.
*/ */
public boolean addWorldParam(String name) { public boolean addWorldParam(String name) {
if (worldBinds == null){
worldBinds = new ArrayList<UniformBinding>();
}
try { try {
worldBinds.add(UniformBinding.valueOf(name)); worldBinds.add(UniformBinding.valueOf(name));
return true; return true;
@ -801,6 +796,7 @@ public class TechniqueDef implements Savable, Cloneable {
clone.paramToDefineId.putAll(paramToDefineId); clone.paramToDefineId.putAll(paramToDefineId);
if (shaderNodes != null) { if (shaderNodes != null) {
clone.shaderNodes = new ArrayList<>();
for (ShaderNode shaderNode : shaderNodes) { for (ShaderNode shaderNode : shaderNodes) {
clone.shaderNodes.add(shaderNode.clone()); clone.shaderNodes.add(shaderNode.clone());
} }
@ -820,10 +816,8 @@ public class TechniqueDef implements Savable, Cloneable {
e.printStackTrace(); e.printStackTrace();
} }
if (worldBinds != null) {
clone.worldBinds = new ArrayList<>(worldBinds.size()); clone.worldBinds = new ArrayList<>(worldBinds.size());
clone.worldBinds.addAll(worldBinds); clone.worldBinds.addAll(worldBinds);
}
return clone; return clone;
} }

@ -115,7 +115,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
Uniform lightProbeData = shader.getUniform("g_LightProbeData"); Uniform lightProbeData = shader.getUniform("g_LightProbeData");
lightProbeData.setVector4Length(1); lightProbeData.setVector4Length(1);
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
//TODO These 2 uniforms should be packed in an array, to ba able to have several probes and blend between them.
Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap"); Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
lightProbe = null; lightProbe = null;
@ -128,16 +130,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
ambientColor.setValue(VarType.Vector4, ambientLightColor); ambientColor.setValue(VarType.Vector4, ambientLightColor);
} }
//If there is a lightProbe in the list we force it's render on the first pass //If there is a lightProbe in the list we force its render on the first pass
if(lightProbe != null){ if(lightProbe != null){
BoundingSphere s = (BoundingSphere)lightProbe.getBounds(); BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f/s.getRadius(), 0); lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.getRadius() + lightProbe.getNbMipMaps(), 0);
shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
//assigning new texture indexes //assigning new texture indexes
int irrUnit = lastTexUnit++;
int pemUnit = lastTexUnit++; int pemUnit = lastTexUnit++;
rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
lightProbeIrrMap.setValue(VarType.Int, irrUnit);
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit); lightProbePemMap.setValue(VarType.Int, pemUnit);
} else { } else {

@ -186,27 +186,24 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
} }
/** /**
* Changes the values of this matrix acording to it's parent. Very similar to the concept of Node/Spatial transforms. * Changes the values of this matrix according to it's parent. Very similar to the concept of Node/Spatial transforms.
* @param parent The parent matrix. * @param parent The parent matrix.
* @return This matrix, after combining. * @return This matrix, after combining.
*/ */
public Transform combineWithParent(Transform parent) { public Transform combineWithParent(Transform parent) {
//applying parent scale to local scale
scale.multLocal(parent.scale); scale.multLocal(parent.scale);
// rot.multLocal(parent.rot); //applying parent rotation to local rotation.
parent.rot.mult(rot, rot); parent.rot.mult(rot, rot);
//applying parent scale to local translation.
// This here, is evil code
// parent
// .rot
// .multLocal(translation)
// .multLocal(parent.scale)
// .addLocal(parent.translation);
translation.multLocal(parent.scale); translation.multLocal(parent.scale);
//applying parent rotation to local translation, then applying parent translation to local translation.
//Note that parent.rot.multLocal(translation) doesn't modify "parent.rot" but "translation"
parent parent
.rot .rot
.multLocal(translation) .multLocal(translation)
.addLocal(parent.translation); .addLocal(parent.translation);
return this; return this;
} }

@ -45,10 +45,6 @@ public class OpenCLObjectManager {
private static final Logger LOG = Logger.getLogger(OpenCLObjectManager.class.getName()); private static final Logger LOG = Logger.getLogger(OpenCLObjectManager.class.getName());
private static final Level LOG_LEVEL1 = Level.FINER; private static final Level LOG_LEVEL1 = Level.FINER;
private static final Level LOG_LEVEL2 = Level.FINE; private static final Level LOG_LEVEL2 = Level.FINE;
/**
* Call Runtime.getRuntime().gc() every these frames
*/
private static final int GC_FREQUENCY = 10;
private static final OpenCLObjectManager INSTANCE = new OpenCLObjectManager(); private static final OpenCLObjectManager INSTANCE = new OpenCLObjectManager();
private OpenCLObjectManager() {} private OpenCLObjectManager() {}
@ -59,7 +55,6 @@ public class OpenCLObjectManager {
private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
private HashSet<OpenCLObjectRef> activeObjects = new HashSet<OpenCLObjectRef>(); private HashSet<OpenCLObjectRef> activeObjects = new HashSet<OpenCLObjectRef>();
private int gcCounter = 0;
private static class OpenCLObjectRef extends PhantomReference<Object> { private static class OpenCLObjectRef extends PhantomReference<Object> {
@ -80,6 +75,7 @@ public class OpenCLObjectManager {
private void deleteObject(OpenCLObjectRef ref) { private void deleteObject(OpenCLObjectRef ref) {
LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser); LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser);
ref.releaser.release(); ref.releaser.release();
ref.clear();
activeObjects.remove(ref); activeObjects.remove(ref);
} }
@ -89,15 +85,6 @@ public class OpenCLObjectManager {
return; //nothing to do return; //nothing to do
} }
gcCounter++;
if (gcCounter >= GC_FREQUENCY) {
//The program is that the OpenCLObjects are so small that they are
//enqueued for finalization very late. Therefore, without this
//hack, we are running out of host memory on the OpenCL side quickly.
gcCounter = 0;
Runtime.getRuntime().gc();
}
int removed = 0; int removed = 0;
while (true) { while (true) {
// Remove objects reclaimed by GC. // Remove objects reclaimed by GC.
@ -117,6 +104,7 @@ public class OpenCLObjectManager {
for (OpenCLObjectRef ref : activeObjects) { for (OpenCLObjectRef ref : activeObjects) {
LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser); LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser);
ref.releaser.release(); ref.releaser.release();
ref.clear();
} }
activeObjects.clear(); activeObjects.clear();
} }

@ -213,13 +213,10 @@ public abstract class Filter implements Savable {
} }
/** /**
* returns the default pass texture format * returns the default pass texture format.
* default is {@link Format#RGB111110F}
*
* @return
*/ */
protected Format getDefaultPassTextureFormat() { protected Format getDefaultPassTextureFormat() {
return Format.RGB111110F; return processor.getDefaultPassTextureFormat();
} }
/** /**

@ -66,7 +66,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
private FrameBuffer renderFrameBuffer; private FrameBuffer renderFrameBuffer;
private Texture2D filterTexture; private Texture2D filterTexture;
private Texture2D depthTexture; private Texture2D depthTexture;
private SafeArrayList<Filter> filters = new SafeArrayList<Filter>(Filter.class); private SafeArrayList<Filter> filters = new SafeArrayList<>(Filter.class);
private AssetManager assetManager; private AssetManager assetManager;
private Picture fsQuad; private Picture fsQuad;
private boolean computeDepth = false; private boolean computeDepth = false;
@ -85,6 +85,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
private AppProfiler prof; private AppProfiler prof;
private Format fbFormat = Format.RGB111110F; private Format fbFormat = Format.RGB111110F;
private Format depthFormat = Format.Depth;
/** /**
* Create a FilterProcessor * Create a FilterProcessor
@ -144,8 +145,12 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
fsQuad.setWidth(1); fsQuad.setWidth(1);
fsQuad.setHeight(1); fsQuad.setHeight(1);
if (fbFormat == Format.RGB111110F && !renderer.getCaps().contains(Caps.PackedFloatTexture)) { if (!renderer.getCaps().contains(Caps.PackedFloatTexture)) {
if (!renderer.getCaps().contains(Caps.FloatTexture)) {
fbFormat = Format.RGB8; fbFormat = Format.RGB8;
} else {
fbFormat = Format.RGB16F;
}
} }
Camera cam = vp.getCamera(); Camera cam = vp.getCamera();
@ -161,6 +166,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
reshape(vp, cam.getWidth(), cam.getHeight()); reshape(vp, cam.getWidth(), cam.getHeight());
} }
public Format getDefaultPassTextureFormat() {
return fbFormat;
}
/** /**
* init the given filter * init the given filter
* @param filter * @param filter
@ -170,7 +179,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
filter.setProcessor(this); filter.setProcessor(this);
if (filter.isRequiresDepthTexture()) { if (filter.isRequiresDepthTexture()) {
if (!computeDepth && renderFrameBuffer != null) { if (!computeDepth && renderFrameBuffer != null) {
depthTexture = new Texture2D(width, height, Format.Depth24); depthTexture = new Texture2D(width, height, depthFormat);
renderFrameBuffer.setDepthTexture(depthTexture); renderFrameBuffer.setDepthTexture(depthTexture);
} }
computeDepth = true; computeDepth = true;
@ -469,20 +478,20 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
renderFrameBufferMS = new FrameBuffer(width, height, numSamples); renderFrameBufferMS = new FrameBuffer(width, height, numSamples);
if (caps.contains(Caps.OpenGL32)) { if (caps.contains(Caps.OpenGL32)) {
Texture2D msColor = new Texture2D(width, height, numSamples, fbFormat); Texture2D msColor = new Texture2D(width, height, numSamples, fbFormat);
Texture2D msDepth = new Texture2D(width, height, numSamples, Format.Depth); Texture2D msDepth = new Texture2D(width, height, numSamples, depthFormat);
renderFrameBufferMS.setDepthTexture(msDepth); renderFrameBufferMS.setDepthTexture(msDepth);
renderFrameBufferMS.setColorTexture(msColor); renderFrameBufferMS.setColorTexture(msColor);
filterTexture = msColor; filterTexture = msColor;
depthTexture = msDepth; depthTexture = msDepth;
} else { } else {
renderFrameBufferMS.setDepthBuffer(Format.Depth); renderFrameBufferMS.setDepthBuffer(depthFormat);
renderFrameBufferMS.setColorBuffer(fbFormat); renderFrameBufferMS.setColorBuffer(fbFormat);
} }
} }
if (numSamples <= 1 || !caps.contains(Caps.OpenGL32)) { if (numSamples <= 1 || !caps.contains(Caps.OpenGL32)) {
renderFrameBuffer = new FrameBuffer(width, height, 1); renderFrameBuffer = new FrameBuffer(width, height, 1);
renderFrameBuffer.setDepthBuffer(Format.Depth); renderFrameBuffer.setDepthBuffer(depthFormat);
filterTexture = new Texture2D(width, height, fbFormat); filterTexture = new Texture2D(width, height, fbFormat);
renderFrameBuffer.setColorTexture(filterTexture); renderFrameBuffer.setColorTexture(filterTexture);
} }

@ -440,23 +440,25 @@ public class Camera implements Savable, Cloneable {
} }
/** /**
* Resizes this camera's view with the given width and height. This is * Resize this camera's view for the specified display size. Invoked by an
* similar to constructing a new camera, but reusing the same Object. This * associated {@link RenderManager} to notify the camera of changes to the
* method is called by an associated {@link RenderManager} to notify the camera of * display dimensions.
* changes in the display dimensions.
* *
* @param width the view width * @param width the new width of the display, in pixels
* @param height the view height * @param height the new height of the display, in pixels
* @param fixAspect If true, the camera's aspect ratio will be recomputed. * @param fixAspect if true, recompute the camera's frustum to preserve its
* Recomputing the aspect ratio requires changing the frustum values. * prior aspect ratio
*/ */
public void resize(int width, int height, boolean fixAspect) { public void resize(int width, int height, boolean fixAspect) {
this.width = width; this.width = width;
this.height = height; this.height = height;
onViewPortChange(); onViewPortChange();
if (fixAspect /*&& !parallelProjection*/) { if (fixAspect) {
frustumRight = frustumTop * ((float) width / height); float h = height * (viewPortTop - viewPortBottom);
float w = width * (viewPortRight - viewPortLeft);
float aspectRatio = w / h;
frustumRight = frustumTop * aspectRatio;
frustumLeft = -frustumRight; frustumLeft = -frustumRight;
onFrustumChange(); onFrustumChange();
} }

@ -128,6 +128,26 @@ public enum Caps {
* Supports OpenGL 4.0 * Supports OpenGL 4.0
*/ */
OpenGL40, OpenGL40,
/**
* Supports OpenGL 4.1
*/
OpenGL41,
/**
* Supports OpenGL 4.2
*/
OpenGL42,
/**
* Supports OpenGL 4.3
*/
OpenGL43,
/**
* Supports OpenGL 4.4
*/
OpenGL44,
/**
* Supports OpenGL 4.5
*/
OpenGL45,
/** /**
* Do not use. * Do not use.
* *
@ -174,6 +194,26 @@ public enum Caps {
* Supports GLSL 4.0 * Supports GLSL 4.0
*/ */
GLSL400, GLSL400,
/**
* Supports GLSL 4.1
*/
GLSL410,
/**
* Supports GLSL 4.2
*/
GLSL420,
/**
* Supports GLSL 4.3
*/
GLSL430,
/**
* Supports GLSL 4.4
*/
GLSL440,
/**
* Supports GLSL 4.5
*/
GLSL450,
/** /**
* Supports reading from textures inside the vertex shader. * Supports reading from textures inside the vertex shader.
*/ */
@ -486,6 +526,18 @@ public enum Caps {
if (!caps.contains(Caps.GLSL150)) return false; if (!caps.contains(Caps.GLSL150)) return false;
case 330: case 330:
if (!caps.contains(Caps.GLSL330)) return false; if (!caps.contains(Caps.GLSL330)) return false;
case 400:
if (!caps.contains(Caps.GLSL400)) return false;
case 410:
if (!caps.contains(Caps.GLSL410)) return false;
case 420:
if (!caps.contains(Caps.GLSL420)) return false;
case 430:
if (!caps.contains(Caps.GLSL430)) return false;
case 440:
if (!caps.contains(Caps.GLSL440)) return false;
case 450:
if (!caps.contains(Caps.GLSL450)) return false;
default: default:
return false; return false;
} }

@ -50,6 +50,7 @@ public interface GL {
public static final int GL_ARRAY_BUFFER = 0x8892; public static final int GL_ARRAY_BUFFER = 0x8892;
public static final int GL_BACK = 0x405; public static final int GL_BACK = 0x405;
public static final int GL_BLEND = 0xBE2; public static final int GL_BLEND = 0xBE2;
public static final int GL_BLUE = 0x1905;
public static final int GL_BYTE = 0x1400; public static final int GL_BYTE = 0x1400;
public static final int GL_CLAMP_TO_EDGE = 0x812F; public static final int GL_CLAMP_TO_EDGE = 0x812F;
public static final int GL_COLOR_BUFFER_BIT = 0x4000; public static final int GL_COLOR_BUFFER_BIT = 0x4000;
@ -173,9 +174,7 @@ public interface GL {
public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
public static final int GL_TEXTURE_BASE_LEVEL = 0x813C;
public static final int GL_TEXTURE_MAG_FILTER = 0x2800; public static final int GL_TEXTURE_MAG_FILTER = 0x2800;
public static final int GL_TEXTURE_MAX_LEVEL = 0x813D;
public static final int GL_TEXTURE_MIN_FILTER = 0x2801; public static final int GL_TEXTURE_MIN_FILTER = 0x2801;
public static final int GL_TEXTURE_WRAP_S = 0x2802; public static final int GL_TEXTURE_WRAP_S = 0x2802;
public static final int GL_TEXTURE_WRAP_T = 0x2803; public static final int GL_TEXTURE_WRAP_T = 0x2803;

@ -65,6 +65,8 @@ public interface GL2 extends GL {
public static final int GL_STACK_OVERFLOW = 0x503; public static final int GL_STACK_OVERFLOW = 0x503;
public static final int GL_STACK_UNDERFLOW = 0x504; public static final int GL_STACK_UNDERFLOW = 0x504;
public static final int GL_TEXTURE_3D = 0x806F; public static final int GL_TEXTURE_3D = 0x806F;
public static final int GL_TEXTURE_BASE_LEVEL = 0x813C;
public static final int GL_TEXTURE_MAX_LEVEL = 0x813D;
public static final int GL_POINT_SPRITE = 0x8861; public static final int GL_POINT_SPRITE = 0x8861;
public static final int GL_TEXTURE_COMPARE_FUNC = 0x884D; public static final int GL_TEXTURE_COMPARE_FUNC = 0x884D;
public static final int GL_TEXTURE_COMPARE_MODE = 0x884C; public static final int GL_TEXTURE_COMPARE_MODE = 0x884C;

@ -86,7 +86,6 @@ public interface GL3 extends GL2 {
public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+ public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+
public void glBindVertexArray(int param1); /// GL3+ public void glBindVertexArray(int param1); /// GL3+
public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+ public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+
public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5); /// GL3+
public void glGenVertexArrays(IntBuffer param1); /// GL3+ public void glGenVertexArrays(IntBuffer param1); /// GL3+
public String glGetString(int param1, int param2); /// GL3+ public String glGetString(int param1, int param2); /// GL3+
} }

@ -95,12 +95,6 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
checkError(); checkError();
} }
@Override
public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5) {
gl3.glFramebufferTextureLayer(param1, param2, param3, param4, param5);
checkError();
}
public void glBlendEquationSeparate(int colorMode, int alphaMode) { public void glBlendEquationSeparate(int colorMode, int alphaMode) {
gl.glBlendEquationSeparate(colorMode, alphaMode); gl.glBlendEquationSeparate(colorMode, alphaMode);
checkError(); checkError();

@ -598,8 +598,15 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
return sync; return sync;
} }
@Override
public void glBlendEquationSeparate(int colorMode, int alphaMode) { public void glBlendEquationSeparate(int colorMode, int alphaMode) {
gl.glBlendEquationSeparate(colorMode, alphaMode); gl.glBlendEquationSeparate(colorMode, alphaMode);
checkError(); checkError();
} }
@Override
public void glFramebufferTextureLayerEXT(int param1, int param2, int param3, int param4, int param5) {
glfbo.glFramebufferTextureLayerEXT(param1, param2, param3, param4, param5);
checkError();
}
} }

@ -61,6 +61,7 @@ public interface GLExt {
public static final int GL_FRAMEBUFFER_SRGB_CAPABLE_EXT = 0x8DBA; public static final int GL_FRAMEBUFFER_SRGB_CAPABLE_EXT = 0x8DBA;
public static final int GL_FRAMEBUFFER_SRGB_EXT = 0x8DB9; public static final int GL_FRAMEBUFFER_SRGB_EXT = 0x8DB9;
public static final int GL_HALF_FLOAT_ARB = 0x140B; public static final int GL_HALF_FLOAT_ARB = 0x140B;
public static final int GL_HALF_FLOAT_OES = 0x8D61;
public static final int GL_LUMINANCE16F_ARB = 0x881E; public static final int GL_LUMINANCE16F_ARB = 0x881E;
public static final int GL_LUMINANCE32F_ARB = 0x8818; public static final int GL_LUMINANCE32F_ARB = 0x8818;
public static final int GL_LUMINANCE_ALPHA16F_ARB = 0x881F; public static final int GL_LUMINANCE_ALPHA16F_ARB = 0x881F;

@ -89,6 +89,7 @@ public interface GLFbo {
public void glDeleteRenderbuffersEXT(IntBuffer param1); public void glDeleteRenderbuffersEXT(IntBuffer param1);
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4); public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4);
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5); public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5);
public void glFramebufferTextureLayerEXT(int target, int attachment, int texture, int level, int layer);
public void glGenFramebuffersEXT(IntBuffer param1); public void glGenFramebuffersEXT(IntBuffer param1);
public void glGenRenderbuffersEXT(IntBuffer param1); public void glGenRenderbuffersEXT(IntBuffer param1);
public void glGenerateMipmapEXT(int param1); public void glGenerateMipmapEXT(int param1);

@ -103,14 +103,19 @@ public final class GLImageFormats {
public static GLImageFormat[][] getFormatsForCaps(EnumSet<Caps> caps) { public static GLImageFormat[][] getFormatsForCaps(EnumSet<Caps> caps) {
GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length]; GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length];
int halfFloatFormat = GLExt.GL_HALF_FLOAT_ARB;
if (caps.contains(Caps.OpenGLES20)) {
halfFloatFormat = GLExt.GL_HALF_FLOAT_OES;
}
// Core Profile Formats (supported by both OpenGL Core 3.3 and OpenGL ES 3.0+) // Core Profile Formats (supported by both OpenGL Core 3.3 and OpenGL ES 3.0+)
if (caps.contains(Caps.CoreProfile)) { if (caps.contains(Caps.CoreProfile)) {
formatSwiz(formatToGL, Format.Alpha8, GL3.GL_R8, GL.GL_RED, GL.GL_UNSIGNED_BYTE); formatSwiz(formatToGL, Format.Alpha8, GL3.GL_R8, GL.GL_RED, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.Luminance8, GL3.GL_R8, GL.GL_RED, GL.GL_UNSIGNED_BYTE); formatSwiz(formatToGL, Format.Luminance8, GL3.GL_R8, GL.GL_RED, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.Luminance8Alpha8, GL3.GL_RG8, GL3.GL_RG, GL.GL_UNSIGNED_BYTE); formatSwiz(formatToGL, Format.Luminance8Alpha8, GL3.GL_RG8, GL3.GL_RG, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.Luminance16F, GL3.GL_R16F, GL.GL_RED, GLExt.GL_HALF_FLOAT_ARB); formatSwiz(formatToGL, Format.Luminance16F, GL3.GL_R16F, GL.GL_RED, halfFloatFormat);
formatSwiz(formatToGL, Format.Luminance32F, GL3.GL_R32F, GL.GL_RED, GL.GL_FLOAT); formatSwiz(formatToGL, Format.Luminance32F, GL3.GL_R32F, GL.GL_RED, GL.GL_FLOAT);
formatSwiz(formatToGL, Format.Luminance16FAlpha16F, GL3.GL_RG16F, GL3.GL_RG, GLExt.GL_HALF_FLOAT_ARB); formatSwiz(formatToGL, Format.Luminance16FAlpha16F, GL3.GL_RG16F, GL3.GL_RG, halfFloatFormat);
formatSrgbSwiz(formatToGL, Format.Luminance8, GLExt.GL_SRGB8_EXT, GL.GL_RED, GL.GL_UNSIGNED_BYTE); formatSrgbSwiz(formatToGL, Format.Luminance8, GLExt.GL_SRGB8_EXT, GL.GL_RED, GL.GL_UNSIGNED_BYTE);
formatSrgbSwiz(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SRGB8_ALPHA8_EXT, GL3.GL_RG, GL.GL_UNSIGNED_BYTE); formatSrgbSwiz(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SRGB8_ALPHA8_EXT, GL3.GL_RG, GL.GL_UNSIGNED_BYTE);
@ -163,6 +168,11 @@ public final class GLImageFormats {
} }
format(formatToGL, Format.RGB8, GLExt.GL_RGBA8, GL.GL_RGB, 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); format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.BGR8, GL2.GL_RGB8, GL2.GL_RGB, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.ARGB8, GLExt.GL_RGBA8, GL2.GL_RGBA, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.BGRA8, GLExt.GL_RGBA8, GL2.GL_RGBA, GL.GL_UNSIGNED_BYTE);
formatSwiz(formatToGL, Format.ABGR8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
} else { } else {
// Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above.. // Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above..
if (!caps.contains(Caps.CoreProfile)) { if (!caps.contains(Caps.CoreProfile)) {
@ -182,33 +192,38 @@ public final class GLImageFormats {
if (caps.contains(Caps.FloatTexture)) { if (caps.contains(Caps.FloatTexture)) {
if (!caps.contains(Caps.CoreProfile)) { if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB); format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, halfFloatFormat);
format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT); 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.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, halfFloatFormat);
} }
format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB); format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, halfFloatFormat);
format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT); 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); format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, halfFloatFormat);
format(formatToGL, Format.RGBA32F, GLExt.GL_RGBA32F_ARB, GL.GL_RGBA, GL.GL_FLOAT); format(formatToGL, Format.RGBA32F, GLExt.GL_RGBA32F_ARB, GL.GL_RGBA, GL.GL_FLOAT);
} }
if (caps.contains(Caps.PackedFloatTexture)) { if (caps.contains(Caps.PackedFloatTexture)) {
format(formatToGL, Format.RGB111110F, GLExt.GL_R11F_G11F_B10F_EXT, GL.GL_RGB, GLExt.GL_UNSIGNED_INT_10F_11F_11F_REV_EXT); format(formatToGL, Format.RGB111110F, GLExt.GL_R11F_G11F_B10F_EXT, GL.GL_RGB, GLExt.GL_UNSIGNED_INT_10F_11F_11F_REV_EXT);
if (caps.contains(Caps.FloatTexture)) { if (caps.contains(Caps.FloatTexture)) {
format(formatToGL, Format.RGB16F_to_RGB111110F, GLExt.GL_R11F_G11F_B10F_EXT, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB); format(formatToGL, Format.RGB16F_to_RGB111110F, GLExt.GL_R11F_G11F_B10F_EXT, GL.GL_RGB, halfFloatFormat);
} }
} }
if (caps.contains(Caps.SharedExponentTexture)) { if (caps.contains(Caps.SharedExponentTexture)) {
format(formatToGL, Format.RGB9E5, GLExt.GL_RGB9_E5_EXT, GL.GL_RGB, GLExt.GL_UNSIGNED_INT_5_9_9_9_REV_EXT); format(formatToGL, Format.RGB9E5, GLExt.GL_RGB9_E5_EXT, GL.GL_RGB, GLExt.GL_UNSIGNED_INT_5_9_9_9_REV_EXT);
if (caps.contains(Caps.FloatTexture)) { if (caps.contains(Caps.FloatTexture)) {
format(formatToGL, Format.RGB16F_to_RGB9E5, GLExt.GL_RGB9_E5_EXT, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB); format(formatToGL, Format.RGB16F_to_RGB9E5, GLExt.GL_RGB9_E5_EXT, GL.GL_RGB, halfFloatFormat);
} }
} }
// Need to check if Caps.DepthTexture is supported prior to using for textures // Need to check if Caps.DepthTexture is supported prior to using for textures
// But for renderbuffers its OK. // But for renderbuffers its OK.
format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT); format(formatToGL, Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT);
// NOTE: OpenGL ES 2.0 does not support DEPTH_COMPONENT as internal format -- fallback to 16-bit depth.
if (caps.contains(Caps.OpenGLES20)) {
format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE);
} else {
format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE);
}
if (caps.contains(Caps.OpenGL20)) { if (caps.contains(Caps.OpenGL20)) {
format(formatToGL, Format.Depth24, GL2.GL_DEPTH_COMPONENT24, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT); format(formatToGL, Format.Depth24, GL2.GL_DEPTH_COMPONENT24, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT);
} }

@ -180,10 +180,13 @@ public final class GLRenderer implements Renderer {
caps.add(Caps.OpenGL20); caps.add(Caps.OpenGL20);
if (oglVer >= 210) { if (oglVer >= 210) {
caps.add(Caps.OpenGL21); caps.add(Caps.OpenGL21);
}
if (oglVer >= 300) { if (oglVer >= 300) {
caps.add(Caps.OpenGL30); caps.add(Caps.OpenGL30);
}
if (oglVer >= 310) { if (oglVer >= 310) {
caps.add(Caps.OpenGL31); caps.add(Caps.OpenGL31);
}
if (oglVer >= 320) { if (oglVer >= 320) {
caps.add(Caps.OpenGL32); caps.add(Caps.OpenGL32);
} }
@ -195,8 +198,20 @@ public final class GLRenderer implements Renderer {
caps.add(Caps.OpenGL40); caps.add(Caps.OpenGL40);
caps.add(Caps.TesselationShader); caps.add(Caps.TesselationShader);
} }
if (oglVer >= 410) {
caps.add(Caps.OpenGL41);
}
if (oglVer >= 420) {
caps.add(Caps.OpenGL42);
} }
if (oglVer >= 430) {
caps.add(Caps.OpenGL43);
} }
if (oglVer >= 440) {
caps.add(Caps.OpenGL44);
}
if (oglVer >= 450) {
caps.add(Caps.OpenGL45);
} }
} }
@ -209,6 +224,16 @@ public final class GLRenderer implements Renderer {
} }
// so that future OpenGL revisions wont break jme3 // so that future OpenGL revisions wont break jme3
// fall through intentional // fall through intentional
case 450:
caps.add(Caps.GLSL450);
case 440:
caps.add(Caps.GLSL440);
case 430:
caps.add(Caps.GLSL430);
case 420:
caps.add(Caps.GLSL420);
case 410:
caps.add(Caps.GLSL410);
case 400: case 400:
caps.add(Caps.GLSL400); caps.add(Caps.GLSL400);
case 330: case 330:
@ -1556,7 +1581,7 @@ public final class GLRenderer implements Renderer {
image.getId(), image.getId(),
0); 0);
} else { } else {
gl3.glFramebufferTextureLayer(GLFbo.GL_FRAMEBUFFER_EXT, glfbo.glFramebufferTextureLayerEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()), convertAttachmentSlot(rb.getSlot()),
image.getId(), image.getId(),
0, 0,
@ -1955,7 +1980,13 @@ public final class GLRenderer implements Renderer {
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private void setupTextureParams(int unit, Texture tex) { private void setupTextureParams(int unit, Texture tex) {
Image image = tex.getImage(); Image image = tex.getImage();
int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); int samples = image != null ? image.getMultiSamples() : 1;
int target = convertTextureType(tex.getType(), samples, -1);
if (samples > 1) {
bindTextureOnly(target, image, unit);
return;
}
boolean haveMips = true; boolean haveMips = true;
if (image != null) { if (image != null) {
@ -2158,6 +2189,9 @@ public final class GLRenderer implements Renderer {
int target = convertTextureType(type, img.getMultiSamples(), -1); int target = convertTextureType(type, img.getMultiSamples(), -1);
bindTextureAndUnit(target, img, unit); bindTextureAndUnit(target, img, unit);
int imageSamples = img.getMultiSamples();
if (imageSamples <= 1) {
if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) { if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
// Image does not have mipmaps, but they are required. // Image does not have mipmaps, but they are required.
// Generate from base level. // Generate from base level.
@ -2169,30 +2203,34 @@ public final class GLRenderer implements Renderer {
// For OpenGL3 and up. // For OpenGL3 and up.
// We'll generate mipmaps via glGenerateMipmapEXT (see below) // We'll generate mipmaps via glGenerateMipmapEXT (see below)
} }
} else if (img.hasMipmaps()) { } else if (caps.contains(Caps.OpenGL20)) {
if (img.hasMipmaps()) {
// Image already has mipmaps, set the max level based on the // Image already has mipmaps, set the max level based on the
// number of mipmaps we have. // number of mipmaps we have.
gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1); gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
} else { } else {
// Image does not have mipmaps and they are not required. // Image does not have mipmaps and they are not required.
// Specify that that the texture has no mipmaps. // Specify that that the texture has no mipmaps.
gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, 0); gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, 0);
}
}
} else {
// Check if graphics card doesn't support multisample textures
if (!caps.contains(Caps.TextureMultisample)) {
throw new RendererException("Multisample textures are not supported by the video hardware");
}
if (img.isGeneratedMipmapsRequired() || img.hasMipmaps()) {
throw new RendererException("Multisample textures do not support mipmaps");
} }
int imageSamples = img.getMultiSamples();
if (imageSamples > 1) {
if (img.getFormat().isDepthFormat()) { if (img.getFormat().isDepthFormat()) {
img.setMultiSamples(Math.min(limits.get(Limits.DepthTextureSamples), imageSamples)); img.setMultiSamples(Math.min(limits.get(Limits.DepthTextureSamples), imageSamples));
} else { } else {
img.setMultiSamples(Math.min(limits.get(Limits.ColorTextureSamples), imageSamples)); img.setMultiSamples(Math.min(limits.get(Limits.ColorTextureSamples), imageSamples));
} }
}
// Check if graphics card doesn't support multisample textures scaleToPot = false;
if (!caps.contains(Caps.TextureMultisample)) {
if (img.getMultiSamples() > 1) {
throw new RendererException("Multisample textures are not supported by the video hardware");
}
} }
// Check if graphics card doesn't support depth textures // Check if graphics card doesn't support depth textures
@ -2567,6 +2605,7 @@ public final class GLRenderer implements Renderer {
} }
switch (indexBuf.getFormat()) { switch (indexBuf.getFormat()) {
case UnsignedByte:
case UnsignedShort: case UnsignedShort:
// OK: Works on all platforms. // OK: Works on all platforms.
break; break;

@ -99,6 +99,7 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glEnableVertexAttribArray", 0); noEnumArgs("glEnableVertexAttribArray", 0);
noEnumArgs("glDisableVertexAttribArray", 0); noEnumArgs("glDisableVertexAttribArray", 0);
noEnumArgs("glVertexAttribPointer", 0, 1, 4, 5); noEnumArgs("glVertexAttribPointer", 0, 1, 4, 5);
noEnumArgs("glVertexAttribDivisorARB", 0, 1);
noEnumArgs("glDrawRangeElements", 1, 2, 3, 5); noEnumArgs("glDrawRangeElements", 1, 2, 3, 5);
noEnumArgs("glDrawArrays", 1, 2); noEnumArgs("glDrawArrays", 1, 2);
noEnumArgs("glDeleteBuffers", 0); noEnumArgs("glDeleteBuffers", 0);
@ -111,6 +112,7 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glRenderbufferStorageMultisampleEXT", 1, 3, 4); noEnumArgs("glRenderbufferStorageMultisampleEXT", 1, 3, 4);
noEnumArgs("glFramebufferRenderbufferEXT", 3); noEnumArgs("glFramebufferRenderbufferEXT", 3);
noEnumArgs("glFramebufferTexture2DEXT", 3, 4); noEnumArgs("glFramebufferTexture2DEXT", 3, 4);
noEnumArgs("glFramebufferTextureLayerEXT", 2, 3, 4);
noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8); noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8);
noEnumArgs("glCreateProgram", -1); noEnumArgs("glCreateProgram", -1);
@ -167,13 +169,14 @@ public final class GLTracer implements InvocationHandler {
* Creates a tracer implementation that wraps OpenGL ES 2. * Creates a tracer implementation that wraps OpenGL ES 2.
* *
* @param glInterface OGL object to wrap * @param glInterface OGL object to wrap
* @param glInterfaceClass The interface to implement * @param glInterfaceClasses The interface(s) to implement
* @return A tracer that implements the given interface * @return A tracer that implements the given interface
*/ */
public static Object createGlesTracer(Object glInterface, Class<?> glInterfaceClass) { public static Object createGlesTracer(Object glInterface, Class<?>... glInterfaceClasses) {
IntMap<String> constMap = generateConstantMap(GL.class, GLFbo.class, GLExt.class); IntMap<String> constMap = generateConstantMap(GL.class, GL2.class, GL3.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), return Proxy.newProxyInstance(
new Class<?>[] { glInterfaceClass }, glInterface.getClass().getClassLoader(),
glInterfaceClasses,
new GLTracer(glInterface, constMap)); new GLTracer(glInterface, constMap));
} }
@ -301,7 +304,8 @@ public final class GLTracer implements InvocationHandler {
// will be printed in darker color // will be printed in darker color
methodName = methodName.substring(2); methodName = methodName.substring(2);
if (methodName.equals("Clear") if (methodName.equals("Clear")
|| methodName.equals("DrawRangeElements")) { || methodName.equals("DrawRangeElements")
|| methodName.equals("DrawElementsInstancedARB")) {
print(methodName); print(methodName);
} else { } else {
if (methodName.endsWith("EXT")) { if (methodName.endsWith("EXT")) {
@ -363,8 +367,8 @@ public final class GLTracer implements InvocationHandler {
printEnum(param); printEnum(param);
print(", "); print(", ");
if (param == GL.GL_TEXTURE_BASE_LEVEL if (param == GL2.GL_TEXTURE_BASE_LEVEL
|| param == GL.GL_TEXTURE_MAX_LEVEL) { || param == GL2.GL_TEXTURE_MAX_LEVEL) {
printInt(value); printInt(value);
} else { } else {
printEnum(value); printEnum(value);

@ -32,7 +32,6 @@
package com.jme3.renderer.opengl; package com.jme3.renderer.opengl;
import com.jme3.renderer.Caps; import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderContext;
import com.jme3.renderer.RendererException; import com.jme3.renderer.RendererException;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
@ -91,7 +90,7 @@ final class TextureUtil {
public GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) { public GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) {
//if the passed format is one kind of depth there isno point in getting the srgb format; //if the passed format is one kind of depth there isno point in getting the srgb format;
isSrgb = isSrgb && fmt != Format.Depth && fmt != Format.Depth16 && fmt != Format.Depth24 && fmt != Format.Depth24Stencil8 && fmt != Format.Depth32 && fmt != Format.Depth32F; isSrgb = isSrgb && !fmt.isDepthFormat();
GLImageFormat glFmt = getImageFormat(fmt, isSrgb); GLImageFormat glFmt = getImageFormat(fmt, isSrgb);
if (glFmt == null && isSrgb) { if (glFmt == null && isSrgb) {
glFmt = getImageFormat(fmt, false); glFmt = getImageFormat(fmt, false);
@ -127,6 +126,14 @@ final class TextureUtil {
gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED);
gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_GREEN); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_GREEN);
break; break;
case ABGR8:
gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_ALPHA);
gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_BLUE);
gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_GREEN);
gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_RED);
break;
default:
throw new UnsupportedOperationException();
} }
} }

@ -31,16 +31,15 @@
*/ */
package com.jme3.scene; package com.jme3.scene;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.asset.ModelKey; import com.jme3.asset.ModelKey;
import com.jme3.export.InputCapsule; import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter; import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter; import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.util.clone.Cloner;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -164,25 +163,24 @@ public class AssetLinkNode extends Node {
@Override @Override
public void read(JmeImporter e) throws IOException { public void read(JmeImporter e) throws IOException {
super.read(e); super.read(e);
InputCapsule capsule = e.getCapsule(this);
BinaryImporter importer = BinaryImporter.getInstance(); final InputCapsule capsule = e.getCapsule(this);
AssetManager loaderManager = e.getAssetManager(); final AssetManager assetManager = e.getAssetManager();
assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>()); assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>());
for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
ModelKey modelKey = it.next(); for (final Iterator<ModelKey> iterator = assetLoaderKeys.iterator(); iterator.hasNext(); ) {
AssetInfo info = loaderManager.locateAsset(modelKey);
Spatial child = null; final ModelKey modelKey = iterator.next();
if (info != null) { final Spatial child = assetManager.loadAsset(modelKey);
child = (Spatial) importer.load(info);
}
if (child != null) { if (child != null) {
child.parent = this; child.parent = this;
children.add(child); children.add(child);
assetChildren.put(modelKey, child); assetChildren.put(modelKey, child);
} else { } else {
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", Logger.getLogger(this.getClass().getName()).log(Level.WARNING,
new Object[]{ modelKey, key }); "Cannot locate {0} for asset link node {1}", new Object[]{modelKey, key});
} }
} }
} }
@ -190,7 +188,7 @@ public class AssetLinkNode extends Node {
@Override @Override
public void write(JmeExporter e) throws IOException { public void write(JmeExporter e) throws IOException {
SafeArrayList<Spatial> childs = children; SafeArrayList<Spatial> childs = children;
children = new SafeArrayList<Spatial>(Spatial.class); children = new SafeArrayList<>(Spatial.class);
super.write(e); super.write(e);
OutputCapsule capsule = e.getCapsule(this); OutputCapsule capsule = e.getCapsule(this);
capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null); capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null);

@ -62,9 +62,9 @@ import com.jme3.util.clone.JmeCloneable;
* Sub geoms can be removed but it may be slower than the normal spatial removing * Sub geoms can be removed but it may be slower than the normal spatial removing
* Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries. * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
* To integrate them in the batch you have to call the batch() method again on the batchNode. * To integrate them in the batch you have to call the batch() method again on the batchNode.
* * <p>
* TODO normal or tangents or both looks a bit weird
* TODO more automagic (batch when needed in the updateLogicalState) * TODO more automagic (batch when needed in the updateLogicalState)
*
* @author Nehon * @author Nehon
*/ */
public class BatchNode extends GeometryGroupNode { public class BatchNode extends GeometryGroupNode {
@ -133,35 +133,44 @@ public class BatchNode extends GeometryGroupNode {
Mesh origMesh = bg.getMesh(); Mesh origMesh = bg.getMesh();
VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position); VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer posBuf = (FloatBuffer) pvb.getData();
VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer normBuf = (FloatBuffer) nvb.getData(); VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position); VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal); VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer onormBuf = (FloatBuffer) onvb.getData(); VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent);
FloatBuffer posBuf = getFloatBuffer(pvb);
FloatBuffer normBuf = getFloatBuffer(nvb);
FloatBuffer tanBuf = getFloatBuffer(tvb);
FloatBuffer oposBuf = getFloatBuffer(opvb);
FloatBuffer onormBuf = getFloatBuffer(onvb);
FloatBuffer otanBuf = getFloatBuffer(otvb);
Matrix4f transformMat = getTransformMatrix(bg); Matrix4f transformMat = getTransformMatrix(bg);
doTransforms(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) { pvb.updateData(posBuf);
VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent); if (nvb != null) {
FloatBuffer tanBuf = (FloatBuffer) tvb.getData(); nvb.updateData(normBuf);
VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent); }
FloatBuffer otanBuf = (FloatBuffer) otvb.getData(); if (tvb != null) {
doTransformsTangents(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
tvb.updateData(tanBuf); tvb.updateData(tanBuf);
} else {
doTransforms(oposBuf, onormBuf, posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
} }
pvb.updateData(posBuf);
nvb.updateData(normBuf);
batch.geometry.updateModelBound(); batch.geometry.updateModelBound();
} }
} }
private FloatBuffer getFloatBuffer(VertexBuffer vb) {
if (vb == null) {
return null;
}
return (FloatBuffer) vb.getData();
}
/** /**
* Batch this batchNode * Batch this batchNode
* every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
@ -257,6 +266,7 @@ public class BatchNode extends GeometryGroupNode {
/** /**
* recursively visit the subgraph and unbatch geometries * recursively visit the subgraph and unbatch geometries
*
* @param s * @param s
*/ */
private void unbatchSubGraph(Spatial s) { private void unbatchSubGraph(Spatial s) {
@ -343,11 +353,10 @@ public class BatchNode extends GeometryGroupNode {
/** /**
* Returns the material that is used for the first batch of this BatchNode * Returns the material that is used for the first batch of this BatchNode
* * <p>
* use getMaterial(Material material,int batchIndex) to get a material from a specific batch * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
* *
* @return the material that is used for the first batch of this BatchNode * @return the material that is used for the first batch of this BatchNode
*
* @see #setMaterial(com.jme3.material.Material) * @see #setMaterial(com.jme3.material.Material)
*/ */
public Material getMaterial() { public Material getMaterial() {
@ -428,14 +437,6 @@ public class BatchNode extends GeometryGroupNode {
+ " primitive types: " + mode + " != " + listMode); + " primitive types: " + mode + " != " + listMode);
} }
mode = listMode; mode = listMode;
//Not needed anymore as lineWidth is now in RenderState and will be taken into account when merging according to the material
// if (mode == Mesh.Mode.Lines) {
// if (lineWidth != 1f && listLineWidth != lineWidth) {
// throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
// + lineWidth + " != " + listLineWidth);
// }
// lineWidth = listLineWidth;
// }
compsForBuf[VertexBuffer.Type.Index.ordinal()] = components; compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
} }
@ -528,53 +529,7 @@ public class BatchNode extends GeometryGroupNode {
} }
} }
private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) { private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bindBufTangents, FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
TempVars vars = TempVars.get();
Vector3f pos = vars.vect1;
Vector3f norm = vars.vect2;
int length = (end - start) * 3;
// offset is given in element units
// convert to be in component units
int offset = start * 3;
bindBufPos.rewind();
bindBufNorm.rewind();
//bufPos.position(offset);
//bufNorm.position(offset);
bindBufPos.get(tmpFloat, 0, length);
bindBufNorm.get(tmpFloatN, 0, length);
int index = 0;
while (index < length) {
pos.x = tmpFloat[index];
norm.x = tmpFloatN[index++];
pos.y = tmpFloat[index];
norm.y = tmpFloatN[index++];
pos.z = tmpFloat[index];
norm.z = tmpFloatN[index];
transform.mult(pos, pos);
transform.multNormal(norm, norm);
index -= 2;
tmpFloat[index] = pos.x;
tmpFloatN[index++] = norm.x;
tmpFloat[index] = pos.y;
tmpFloatN[index++] = norm.y;
tmpFloat[index] = pos.z;
tmpFloatN[index++] = norm.z;
}
vars.release();
bufPos.position(offset);
//using bulk put as it's faster
bufPos.put(tmpFloat, 0, length);
bufNorm.position(offset);
//using bulk put as it's faster
bufNorm.put(tmpFloatN, 0, length);
}
private void doTransformsTangents(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bindBufTangents,FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
Vector3f pos = vars.vect1; Vector3f pos = vars.vect1;
Vector3f norm = vars.vect2; Vector3f norm = vars.vect2;
@ -588,61 +543,77 @@ public class BatchNode extends GeometryGroupNode {
int offset = start * 3; int offset = start * 3;
int tanOffset = start * 4; int tanOffset = start * 4;
bindBufPos.rewind(); bindBufPos.rewind();
bindBufNorm.rewind();
bindBufTangents.rewind();
bindBufPos.get(tmpFloat, 0, length); bindBufPos.get(tmpFloat, 0, length);
if (bindBufNorm != null) {
bindBufNorm.rewind();
bindBufNorm.get(tmpFloatN, 0, length); bindBufNorm.get(tmpFloatN, 0, length);
}
if (bindBufTangents != null) {
bindBufTangents.rewind();
bindBufTangents.get(tmpFloatT, 0, tanLength); bindBufTangents.get(tmpFloatT, 0, tanLength);
}
int index = 0; int index = 0;
int tanIndex = 0; int tanIndex = 0;
while (index < length) { int index1, index2, tanIndex1, tanIndex2;
pos.x = tmpFloat[index];
norm.x = tmpFloatN[index++];
pos.y = tmpFloat[index];
norm.y = tmpFloatN[index++];
pos.z = tmpFloat[index];
norm.z = tmpFloatN[index];
tan.x = tmpFloatT[tanIndex++]; while (index < length) {
tan.y = tmpFloatT[tanIndex++]; index1 = index + 1;
tan.z = tmpFloatT[tanIndex++]; index2 = index + 2;
pos.x = tmpFloat[index];
pos.y = tmpFloat[index1];
pos.z = tmpFloat[index2];
transform.mult(pos, pos); transform.mult(pos, pos);
transform.multNormal(norm, norm);
transform.multNormal(tan, tan);
index -= 2;
tanIndex -= 3;
tmpFloat[index] = pos.x; tmpFloat[index] = pos.x;
tmpFloatN[index++] = norm.x; tmpFloat[index1] = pos.y;
tmpFloat[index] = pos.y; tmpFloat[index2] = pos.z;
tmpFloatN[index++] = norm.y;
tmpFloat[index] = pos.z;
tmpFloatN[index++] = norm.z;
tmpFloatT[tanIndex++] = tan.x; if (bindBufNorm != null) {
tmpFloatT[tanIndex++] = tan.y; norm.x = tmpFloatN[index];
tmpFloatT[tanIndex++] = tan.z; norm.y = tmpFloatN[index1];
norm.z = tmpFloatN[index2];
transform.multNormal(norm, norm);
tmpFloatN[index] = norm.x;
tmpFloatN[index1] = norm.y;
tmpFloatN[index2] = norm.z;
}
index += 3;
//Skipping 4th element of tangent buffer (handedness) if (bindBufTangents != null) {
tanIndex++; tanIndex1 = tanIndex + 1;
tanIndex2 = tanIndex + 2;
tan.x = tmpFloatT[tanIndex];
tan.y = tmpFloatT[tanIndex1];
tan.z = tmpFloatT[tanIndex2];
transform.multNormal(tan, tan);
tmpFloatT[tanIndex] = tan.x;
tmpFloatT[tanIndex1] = tan.y;
tmpFloatT[tanIndex2] = tan.z;
tanIndex += 4;
}
} }
vars.release(); vars.release();
bufPos.position(offset);
//using bulk put as it's faster //using bulk put as it's faster
bufPos.position(offset);
bufPos.put(tmpFloat, 0, length); bufPos.put(tmpFloat, 0, length);
if (bindBufNorm != null) {
bufNorm.position(offset); bufNorm.position(offset);
//using bulk put as it's faster
bufNorm.put(tmpFloatN, 0, length); bufNorm.put(tmpFloatN, 0, length);
}
if (bindBufTangents != null) {
bufTangents.position(tanOffset); bufTangents.position(tanOffset);
//using bulk put as it's faster
bufTangents.put(tmpFloatT, 0, tanLength); bufTangents.put(tmpFloatT, 0, tanLength);
} }
}
private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
@ -653,11 +624,11 @@ public class BatchNode extends GeometryGroupNode {
offset *= componentSize; offset *= componentSize;
for (int i = 0; i < inBuf.limit() / componentSize; i++) { for (int i = 0; i < inBuf.limit() / componentSize; i++) {
pos.x = inBuf.get(i * componentSize + 0); pos.x = inBuf.get(i * componentSize);
pos.y = inBuf.get(i * componentSize + 1); pos.y = inBuf.get(i * componentSize + 1);
pos.z = inBuf.get(i * componentSize + 2); pos.z = inBuf.get(i * componentSize + 2);
outBuf.put(offset + i * componentSize + 0, pos.x); outBuf.put(offset + i * componentSize, pos.x);
outBuf.put(offset + i * componentSize + 1, pos.y); outBuf.put(offset + i * componentSize + 1, pos.y);
outBuf.put(offset + i * componentSize + 2, pos.z); outBuf.put(offset + i * componentSize + 2, pos.z);
} }
@ -667,6 +638,7 @@ public class BatchNode extends GeometryGroupNode {
protected class Batch implements JmeCloneable { protected class Batch implements JmeCloneable {
/** /**
* update the batchesByGeom map for this batch with the given List of geometries * update the batchesByGeom map for this batch with the given List of geometries
*
* @param list * @param list
*/ */
void updateGeomList(List<Geometry> list) { void updateGeomList(List<Geometry> list) {
@ -676,6 +648,7 @@ public class BatchNode extends GeometryGroupNode {
} }
} }
} }
Geometry geometry; Geometry geometry;
public final Geometry getGeometry() { public final Geometry getGeometry() {

@ -53,6 +53,7 @@ public class CameraNode extends Node {
* Serialization only. Do not use. * Serialization only. Do not use.
*/ */
public CameraNode() { public CameraNode() {
super();
} }
public CameraNode(String name, Camera camera) { public CameraNode(String name, Camera camera) {

@ -398,6 +398,9 @@ public class Geometry extends Spatial {
// Compute the cached world matrix // Compute the cached world matrix
cachedWorldMat.loadIdentity(); cachedWorldMat.loadIdentity();
if (ignoreTransform) {
return;
}
cachedWorldMat.setRotationQuaternion(worldTransform.getRotation()); cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
cachedWorldMat.setTranslation(worldTransform.getTranslation()); cachedWorldMat.setTranslation(worldTransform.getTranslation());

@ -53,6 +53,7 @@ import com.jme3.util.IntMap.Entry;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.nio.*; import java.nio.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -331,6 +332,15 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
this.modeStart = cloner.clone(modeStart); this.modeStart = cloner.clone(modeStart);
} }
/**
* @param forSoftwareAnim
* @deprecated use generateBindPose();
*/
@Deprecated
public void generateBindPose(boolean forSoftwareAnim) {
generateBindPose();
}
/** /**
* Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal}, * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
* and {@link Type#BindPoseTangent} * and {@link Type#BindPoseTangent}
@ -338,11 +348,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* buffers already set on the mesh. * buffers already set on the mesh.
* This method does nothing if the mesh has no bone weight or index * This method does nothing if the mesh has no bone weight or index
* buffers. * buffers.
*
* @param forSoftwareAnim Should be true if the bind pose is to be generated.
*/ */
public void generateBindPose(boolean forSoftwareAnim){ public void generateBindPose() {
if (forSoftwareAnim){
VertexBuffer pos = getBuffer(Type.Position); VertexBuffer pos = getBuffer(Type.Position);
if (pos == null || getBuffer(Type.BoneIndex) == null) { if (pos == null || getBuffer(Type.BoneIndex) == null) {
// ignore, this mesh doesn't have positional data // ignore, this mesh doesn't have positional data
@ -382,7 +389,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
setBuffer(bindTangents); setBuffer(bindTangents);
tangents.setUsage(Usage.Stream); tangents.setUsage(Usage.Stream);
}// else hardware setup does nothing, mesh already in bind pose }// else hardware setup does nothing, mesh already in bind pose
}
} }
/** /**
@ -396,11 +403,20 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
// convert indices to ubytes on the heap // convert indices to ubytes on the heap
VertexBuffer indices = getBuffer(Type.BoneIndex); VertexBuffer indices = getBuffer(Type.BoneIndex);
if (!indices.getData().hasArray()) { if (!indices.getData().hasArray()) {
if (indices.getFormat() == Format.UnsignedByte) {
ByteBuffer originalIndex = (ByteBuffer) indices.getData(); ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
originalIndex.clear(); originalIndex.clear();
arrayIndex.put(originalIndex); arrayIndex.put(originalIndex);
indices.updateData(arrayIndex); indices.updateData(arrayIndex);
} else {
//bone indices can be stored in an UnsignedShort buffer
ShortBuffer originalIndex = (ShortBuffer) indices.getData();
ShortBuffer arrayIndex = ShortBuffer.allocate(originalIndex.capacity());
originalIndex.clear();
arrayIndex.put(originalIndex);
indices.updateData(arrayIndex);
}
} }
indices.setUsage(Usage.CpuOnly); indices.setUsage(Usage.CpuOnly);
@ -429,13 +445,24 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
//if HWBoneIndex and HWBoneWeight are empty, we setup them as direct //if HWBoneIndex and HWBoneWeight are empty, we setup them as direct
//buffers with software anim buffers data //buffers with software anim buffers data
VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex); VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex);
Buffer result;
if (indicesHW.getData() == null) { if (indicesHW.getData() == null) {
VertexBuffer indices = getBuffer(Type.BoneIndex); VertexBuffer indices = getBuffer(Type.BoneIndex);
if (indices.getFormat() == Format.UnsignedByte) {
ByteBuffer originalIndex = (ByteBuffer) indices.getData(); ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer directIndex = BufferUtils.createByteBuffer(originalIndex.capacity()); ByteBuffer directIndex = BufferUtils.createByteBuffer(originalIndex.capacity());
originalIndex.clear(); originalIndex.clear();
directIndex.put(originalIndex); directIndex.put(originalIndex);
indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex); result = directIndex;
} else {
//bone indices can be stored in an UnsignedShort buffer
ShortBuffer originalIndex = (ShortBuffer) indices.getData();
ShortBuffer directIndex = BufferUtils.createShortBuffer(originalIndex.capacity());
originalIndex.clear();
directIndex.put(originalIndex);
result = directIndex;
}
indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), result);
} }
VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight); VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight);
@ -986,6 +1013,18 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
BoundingVolume worldBound, BoundingVolume worldBound,
CollisionResults results){ CollisionResults results){
switch (mode) {
case Points:
case Lines:
case LineStrip:
case LineLoop:
/*
* Collisions can be detected only with triangles,
* and there are no triangles in this mesh.
*/
return 0;
}
if (getVertexCount() == 0) { if (getVertexCount() == 0) {
return 0; return 0;
} }
@ -1420,7 +1459,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
return false; // no bone animation data return false; // no bone animation data
} }
ByteBuffer boneIndexBuffer = (ByteBuffer) biBuf.getData(); IndexBuffer boneIndexBuffer = IndexBuffer.wrapIndexBuffer(biBuf.getData());
boneIndexBuffer.rewind(); boneIndexBuffer.rewind();
int numBoneIndices = boneIndexBuffer.remaining(); int numBoneIndices = boneIndexBuffer.remaining();
assert numBoneIndices % 4 == 0 : numBoneIndices; assert numBoneIndices % 4 == 0 : numBoneIndices;
@ -1433,10 +1472,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
/* /*
* Test each vertex to determine whether the bone affects it. * Test each vertex to determine whether the bone affects it.
*/ */
byte biByte = (byte) boneIndex; // bone indices wrap after 127 int biByte = boneIndex;
for (int vIndex = 0; vIndex < numVertices; vIndex++) { for (int vIndex = 0; vIndex < numVertices; vIndex++) {
for (int wIndex = 0; wIndex < 4; wIndex++) { for (int wIndex = 0; wIndex < 4; wIndex++) {
byte bIndex = boneIndexBuffer.get(); int bIndex = boneIndexBuffer.get();
float weight = weightBuffer.get(); float weight = weightBuffer.get();
if (wIndex < maxNumWeights && bIndex == biByte && weight != 0f) { if (wIndex < maxNumWeights && bIndex == biByte && weight != 0f) {
return true; return true;

@ -42,8 +42,9 @@ import com.jme3.light.SpotLight;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import java.io.IOException; import java.io.IOException;
/** /**
@ -54,7 +55,10 @@ import java.io.IOException;
*/ */
public class LightControl extends AbstractControl { public class LightControl extends AbstractControl {
public static enum ControlDirection { private static final String CONTROL_DIR_NAME = "controlDir";
private static final String LIGHT_NAME = "light";
public enum ControlDirection {
/** /**
* Means, that the Light's transform is "copied" * Means, that the Light's transform is "copied"
@ -67,6 +71,7 @@ public class LightControl extends AbstractControl {
*/ */
SpatialToLight; SpatialToLight;
} }
private Light light; private Light light;
private ControlDirection controlDir = ControlDirection.SpatialToLight; private ControlDirection controlDir = ControlDirection.SpatialToLight;
@ -113,7 +118,7 @@ public class LightControl extends AbstractControl {
if (spatial != null && light != null) { if (spatial != null && light != null) {
switch (controlDir) { switch (controlDir) {
case SpatialToLight: case SpatialToLight:
spatialTolight(light); spatialToLight(light);
break; break;
case LightToSpatial: case LightToSpatial:
lightToSpatial(light); lightToSpatial(light);
@ -122,22 +127,29 @@ public class LightControl extends AbstractControl {
} }
} }
private void spatialTolight(Light light) { private void spatialToLight(Light light) {
final Vector3f worldTranslation = spatial.getWorldTranslation();
if (light instanceof PointLight) { if (light instanceof PointLight) {
((PointLight) light).setPosition(spatial.getWorldTranslation()); ((PointLight) light).setPosition(worldTranslation);
return;
} }
TempVars vars = TempVars.get();
final TempVars vars = TempVars.get();
final Vector3f vec = vars.vect1;
if (light instanceof DirectionalLight) { if (light instanceof DirectionalLight) {
((DirectionalLight) light).setDirection(vars.vect1.set(spatial.getWorldTranslation()).multLocal(-1.0f)); ((DirectionalLight) light).setDirection(vec.set(worldTranslation).multLocal(-1.0f));
} }
if (light instanceof SpotLight) { if (light instanceof SpotLight) {
((SpotLight) light).setPosition(spatial.getWorldTranslation()); final SpotLight spotLight = (SpotLight) light;
((SpotLight) light).setDirection(spatial.getWorldRotation().multLocal(vars.vect1.set(Vector3f.UNIT_Y).multLocal(-1))); spotLight.setPosition(worldTranslation);
spotLight.setDirection(spatial.getWorldRotation().multLocal(vec.set(Vector3f.UNIT_Y).multLocal(-1)));
} }
vars.release();
vars.release();
} }
private void lightToSpatial(Light light) { private void lightToSpatial(Light light) {
@ -158,8 +170,6 @@ public class LightControl extends AbstractControl {
} }
vars.release(); vars.release();
//TODO add code for Spot light here when it's done //TODO add code for Spot light here when it's done
} }
@Override @Override
@ -167,16 +177,11 @@ public class LightControl extends AbstractControl {
// nothing to do // nothing to do
} }
// default implementation from AbstractControl is equivalent @Override
//@Override public void cloneFields(final Cloner cloner, final Object original) {
//public Control cloneForSpatial(Spatial newSpatial) { super.cloneFields(cloner, original);
// LightControl control = new LightControl(light, controlDir); light = cloner.clone(light);
// control.setSpatial(newSpatial); }
// control.setEnabled(isEnabled());
// return control;
//}
private static final String CONTROL_DIR_NAME = "controlDir";
private static final String LIGHT_NAME = "light";
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {

@ -32,9 +32,9 @@
package com.jme3.scene.debug; package com.jme3.scene.debug;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
@ -45,6 +45,9 @@ import java.nio.ShortBuffer;
*/ */
public class Grid extends Mesh { public class Grid extends Mesh {
public Grid() {
}
/** /**
* Creates a grid debug shape. * Creates a grid debug shape.
* @param xLines * @param xLines

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009-2012 jMonkeyEngine * Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -106,6 +106,9 @@ public class WireSphere extends Mesh {
// pb.put(0).put(0).put(radius); // pb.put(0).put(0).put(radius);
// pb.put(0).put(0).put(-radius); // pb.put(0).put(0).put(-radius);
/*
* Update vertex positions for the great circle in the X-Y plane.
*/
float rate = FastMath.TWO_PI / (float) samples; float rate = FastMath.TWO_PI / (float) samples;
float angle = 0; float angle = 0;
for (int i = 0; i < samples; i++) { for (int i = 0; i < samples; i++) {
@ -114,7 +117,9 @@ public class WireSphere extends Mesh {
pb.put(x).put(y).put(0); pb.put(x).put(y).put(0);
angle += rate; angle += rate;
} }
/*
* Update vertex positions for the great circle in the Y-Z plane.
*/
angle = 0; angle = 0;
for (int i = 0; i < samples; i++) { for (int i = 0; i < samples; i++) {
float x = radius * FastMath.cos(angle); float x = radius * FastMath.cos(angle);
@ -122,23 +127,20 @@ public class WireSphere extends Mesh {
pb.put(0).put(x).put(y); pb.put(0).put(x).put(y);
angle += rate; angle += rate;
} }
/*
* Update vertex positions for 'zSamples' parallel circles.
*/
float zRate = (radius * 2) / (float) (zSamples); float zRate = (radius * 2) / (float) (zSamples);
float zHeight = -radius + (zRate / 2f); float zHeight = -radius + (zRate / 2f);
float rb = 1f / zSamples; float rb = 1f / zSamples;
float b = rb / 2f; float b = rb / 2f;
for (int k = 0; k < zSamples; k++) { for (int k = 0; k < zSamples; k++) {
angle = 0; angle = 0;
float scale = FastMath.sin(b * FastMath.PI); float scale = 2f * FastMath.sqrt(b - b * b);
for (int i = 0; i < samples; i++) { for (int i = 0; i < samples; i++) {
float x = radius * FastMath.cos(angle); float x = radius * FastMath.cos(angle);
float y = radius * FastMath.sin(angle); float y = radius * FastMath.sin(angle);
pb.put(x * scale).put(zHeight).put(y * scale); pb.put(x * scale).put(zHeight).put(y * scale);
angle += rate; angle += rate;
} }
zHeight += zRate; zHeight += zRate;

@ -0,0 +1,417 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $
package com.jme3.scene.debug.custom;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;
import static com.jme3.util.BufferUtils.*;
import java.io.IOException;
import java.nio.FloatBuffer;
/**
* A simple cylinder, defined by it's height and radius.
* (Ported to jME3)
*
* @author Mark Powell
* @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
*/
public class BoneShape extends Mesh {
private int axisSamples;
private int radialSamples;
private float radius;
private float radius2;
private float height;
private boolean closed;
private boolean inverted;
/**
* Default constructor for serialization only. Do not use.
*/
public BoneShape() {
}
/**
* Creates a new Cylinder. By default its center is the origin. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information.
*
* @param axisSamples Number of triangle samples along the axis.
* @param radialSamples Number of triangle samples along the radial.
* @param radius The radius of the cylinder.
* @param height The cylinder's height.
*/
public BoneShape(int axisSamples, int radialSamples,
float radius, float height) {
this(axisSamples, radialSamples, radius, height, false);
}
/**
* Creates a new Cylinder. By default its center is the origin. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information. <br>
* If the cylinder is closed the texture is split into axisSamples parts:
* top most and bottom most part is used for top and bottom of the cylinder,
* rest of the texture for the cylinder wall. The middle of the top is
* mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
* a suited distorted texture.
*
* @param axisSamples Number of triangle samples along the axis.
* @param radialSamples Number of triangle samples along the radial.
* @param radius The radius of the cylinder.
* @param height The cylinder's height.
* @param closed true to create a cylinder with top and bottom surface
*/
public BoneShape(int axisSamples, int radialSamples,
float radius, float height, boolean closed) {
this(axisSamples, radialSamples, radius, height, closed, false);
}
/**
* Creates a new Cylinder. By default its center is the origin. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information. <br>
* If the cylinder is closed the texture is split into axisSamples parts:
* top most and bottom most part is used for top and bottom of the cylinder,
* rest of the texture for the cylinder wall. The middle of the top is
* mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
* a suited distorted texture.
*
* @param axisSamples Number of triangle samples along the axis.
* @param radialSamples Number of triangle samples along the radial.
* @param radius The radius of the cylinder.
* @param height The cylinder's height.
* @param closed true to create a cylinder with top and bottom surface
* @param inverted true to create a cylinder that is meant to be viewed from the
* interior.
*/
public BoneShape(int axisSamples, int radialSamples,
float radius, float height, boolean closed, boolean inverted) {
this(axisSamples, radialSamples, radius, radius, height, closed, inverted);
}
public BoneShape(int axisSamples, int radialSamples,
float radius, float radius2, float height, boolean closed, boolean inverted) {
super();
updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);
}
/**
* @return the number of samples along the cylinder axis
*/
public int getAxisSamples() {
return axisSamples;
}
/**
* @return Returns the height.
*/
public float getHeight() {
return height;
}
/**
* @return number of samples around cylinder
*/
public int getRadialSamples() {
return radialSamples;
}
/**
* @return Returns the radius.
*/
public float getRadius() {
return radius;
}
public float getRadius2() {
return radius2;
}
/**
* @return true if end caps are used.
*/
public boolean isClosed() {
return closed;
}
/**
* @return true if normals and uvs are created for interior use
*/
public boolean isInverted() {
return inverted;
}
/**
* Rebuilds the cylinder based on a new set of parameters.
*
* @param axisSamples the number of samples along the axis.
* @param radialSamples the number of samples around the radial.
* @param radius the radius of the bottom of the cylinder.
* @param radius2 the radius of the top of the cylinder.
* @param height the cylinder's height.
* @param closed should the cylinder have top and bottom surfaces.
* @param inverted is the cylinder is meant to be viewed from the inside.
*/
public void updateGeometry(int axisSamples, int radialSamples,
float radius, float radius2, float height, boolean closed, boolean inverted) {
this.axisSamples = axisSamples + (closed ? 2 : 0);
this.radialSamples = radialSamples;
this.radius = radius;
this.radius2 = radius2;
this.height = height;
this.closed = closed;
this.inverted = inverted;
// VertexBuffer pvb = getBuffer(Type.Position);
// VertexBuffer nvb = getBuffer(Type.Normal);
// VertexBuffer tvb = getBuffer(Type.TexCoord);
// Vertices
int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
// Normals
setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
// Texture co-ordinates
setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
//Color
setBuffer(Type.Color, 4, createFloatBuffer(vertCount * 4));
// generate geometry
float inverseRadial = 1.0f / radialSamples;
float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
float halfHeight = 0.5f * height;
// Generate points on the unit circle to be used in computing the mesh
// points on a cylinder slice.
float[] sin = new float[radialSamples + 1];
float[] cos = new float[radialSamples + 1];
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
float angle = FastMath.TWO_PI * inverseRadial * radialCount;
cos[radialCount] = FastMath.cos(angle);
sin[radialCount] = FastMath.sin(angle);
}
sin[radialSamples] = sin[0];
cos[radialSamples] = cos[0];
// calculate normals
Vector3f[] vNormals = null;
Vector3f vNormal = Vector3f.UNIT_Z;
if ((height != 0.0f) && (radius != radius2)) {
vNormals = new Vector3f[radialSamples];
Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
Vector3f vRadial = new Vector3f();
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
Vector3f vRadius = vRadial.mult(radius);
Vector3f vRadius2 = vRadial.mult(radius2);
Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
vNormals[radialCount] = vMantle.cross(vTangent).normalize();
}
}
FloatBuffer nb = getFloatBuffer(Type.Normal);
FloatBuffer pb = getFloatBuffer(Type.Position);
FloatBuffer tb = getFloatBuffer(Type.TexCoord);
FloatBuffer cb = getFloatBuffer(Type.Color);
cb.rewind();
for (int i = 0; i < vertCount; i++) {
cb.put(0.05f).put(0.05f).put(0.05f).put(1f);
}
// generate the cylinder itself
Vector3f tempNormal = new Vector3f();
for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
float axisFraction;
float axisFractionTexture;
int topBottom = 0;
if (!closed) {
axisFraction = axisCount * inverseAxisLess; // in [0,1]
axisFractionTexture = axisFraction;
} else {
if (axisCount == 0) {
topBottom = -1; // bottom
axisFraction = 0;
axisFractionTexture = inverseAxisLessTexture;
} else if (axisCount == axisSamples - 1) {
topBottom = 1; // top
axisFraction = 1;
axisFractionTexture = 1 - inverseAxisLessTexture;
} else {
axisFraction = (axisCount - 1) * inverseAxisLess;
axisFractionTexture = axisCount * inverseAxisLessTexture;
}
}
// compute center of slice
float z = height * axisFraction;
Vector3f sliceCenter = new Vector3f(0, 0, z);
// compute slice vertices with duplication at end point
int save = i;
for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
float radialFraction = radialCount * inverseRadial; // in [0,1)
tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
if (vNormals != null) {
vNormal = vNormals[radialCount];
} else if (radius == radius2) {
vNormal = tempNormal;
}
if (topBottom == 0) {
if (!inverted)
nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
else
nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
} else {
nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
}
tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
.addLocal(sliceCenter);
pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
tb.put((inverted ? 1 - radialFraction : radialFraction))
.put(axisFractionTexture);
}
BufferUtils.copyInternalVector3(pb, save, i);
BufferUtils.copyInternalVector3(nb, save, i);
tb.put((inverted ? 0.0f : 1.0f))
.put(axisFractionTexture);
}
if (closed) {
pb.put(0).put(0).put(-halfHeight); // bottom center
nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
tb.put(0.5f).put(0);
pb.put(0).put(0).put(halfHeight); // top center
nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
tb.put(0.5f).put(1);
}
IndexBuffer ib = getIndexBuffer();
int index = 0;
// Connectivity
for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
int i0 = axisStart;
int i1 = i0 + 1;
axisStart += radialSamples + 1;
int i2 = axisStart;
int i3 = i2 + 1;
for (int i = 0; i < radialSamples; i++) {
if (closed && axisCount == 0) {
if (!inverted) {
ib.put(index++, i0++);
ib.put(index++, vertCount - 2);
ib.put(index++, i1++);
} else {
ib.put(index++, i0++);
ib.put(index++, i1++);
ib.put(index++, vertCount - 2);
}
} else if (closed && axisCount == axisSamples - 2) {
ib.put(index++, i2++);
ib.put(index++, inverted ? vertCount - 1 : i3++);
ib.put(index++, inverted ? i3++ : vertCount - 1);
} else {
ib.put(index++, i0++);
ib.put(index++, inverted ? i2 : i1);
ib.put(index++, inverted ? i1 : i2);
ib.put(index++, i1++);
ib.put(index++, inverted ? i2++ : i3++);
ib.put(index++, inverted ? i3++ : i2++);
}
}
}
updateBound();
}
@Override
public void read(JmeImporter e) throws IOException {
super.read(e);
InputCapsule capsule = e.getCapsule(this);
axisSamples = capsule.readInt("axisSamples", 0);
radialSamples = capsule.readInt("radialSamples", 0);
radius = capsule.readFloat("radius", 0);
radius2 = capsule.readFloat("radius2", 0);
height = capsule.readFloat("height", 0);
closed = capsule.readBoolean("closed", false);
inverted = capsule.readBoolean("inverted", false);
}
@Override
public void write(JmeExporter e) throws IOException {
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
capsule.write(axisSamples, "axisSamples", 0);
capsule.write(radialSamples, "radialSamples", 0);
capsule.write(radius, "radius", 0);
capsule.write(radius2, "radius2", 0);
capsule.write(height, "height", 0);
capsule.write(closed, "closed", false);
capsule.write(inverted, "inverted", false);
}
}

@ -0,0 +1,238 @@
package com.jme3.scene.debug.custom;
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.Map;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.bounding.*;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Sphere;
import static com.jme3.util.BufferUtils.createFloatBuffer;
import java.nio.FloatBuffer;
import java.util.HashMap;
/**
* The class that displays either wires between the bones' heads if no length
* data is supplied and full bones' shapes otherwise.
*/
public class SkeletonBone extends Node {
/**
* The skeleton to be displayed.
*/
private Skeleton skeleton;
/**
* The map between the bone index and its length.
*/
private Map<Bone, Node> boneNodes = new HashMap<Bone, Node>();
private Map<Node, Bone> nodeBones = new HashMap<Node, Bone>();
private Node selectedNode = null;
private boolean guessBonesOrientation = false;
/**
* Creates a wire with bone lengths data. If the data is supplied then the
* wires will show each full bone (from head to tail).
*
* @param skeleton the skeleton that will be shown
* @param boneLengths a map between the bone's index and the bone's length
*/
public SkeletonBone(Skeleton skeleton, Map<Integer, Float> boneLengths, boolean guessBonesOrientation) {
this.skeleton = skeleton;
this.skeleton.reset();
this.skeleton.updateWorldVectors();
this.guessBonesOrientation = guessBonesOrientation;
BoneShape boneShape = new BoneShape(5, 12, 0.02f, 0.07f, 1f, false, false);
Sphere jointShape = new Sphere(10, 10, 0.1f);
jointShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(jointShape.getVertexCount() * 4));
FloatBuffer cb = jointShape.getFloatBuffer(VertexBuffer.Type.Color);
cb.rewind();
for (int i = 0; i < jointShape.getVertexCount(); i++) {
cb.put(0.05f).put(0.05f).put(0.05f).put(1f);
}
for (Bone bone : skeleton.getRoots()) {
createSkeletonGeoms(bone, boneShape, jointShape, boneLengths, skeleton, this, guessBonesOrientation);
}
this.updateModelBound();
Sphere originShape = new Sphere(10, 10, 0.02f);
originShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(originShape.getVertexCount() * 4));
cb = originShape.getFloatBuffer(VertexBuffer.Type.Color);
cb.rewind();
for (int i = 0; i < jointShape.getVertexCount(); i++) {
cb.put(0.4f).put(0.4f).put(0.05f).put(1f);
}
Geometry origin = new Geometry("origin", originShape);
BoundingVolume bv = this.getWorldBound();
float scale = 1;
if (bv.getType() == BoundingVolume.Type.AABB) {
BoundingBox bb = (BoundingBox) bv;
scale = (bb.getXExtent() + bb.getYExtent() + bb.getZExtent()) / 3f;
} else if (bv.getType() == BoundingVolume.Type.Sphere) {
BoundingSphere bs = (BoundingSphere) bv;
scale = bs.getRadius();
}
origin.scale(scale);
attachChild(origin);
}
protected final void createSkeletonGeoms(Bone bone, Mesh boneShape, Mesh jointShape, Map<Integer, Float> boneLengths, Skeleton skeleton, Node parent, boolean guessBonesOrientation) {
if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
//BVH skeleton have a useless end point bone named Site
return;
}
Node n = new Node(bone.getName() + "Node");
Geometry bGeom = new Geometry(bone.getName(), boneShape);
Geometry jGeom = new Geometry(bone.getName() + "Joint", jointShape);
n.setLocalTranslation(bone.getLocalPosition());
n.setLocalRotation(bone.getLocalRotation());
float boneLength = boneLengths.get(skeleton.getBoneIndex(bone));
n.setLocalScale(bone.getLocalScale());
bGeom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X).normalizeLocal());
if (guessBonesOrientation) {
//One child only, the bone direction is from the parent joint to the child joint.
if (bone.getChildren().size() == 1) {
Vector3f v = bone.getChildren().get(0).getLocalPosition();
Quaternion q = new Quaternion();
q.lookAt(v, Vector3f.UNIT_Z);
bGeom.setLocalRotation(q);
boneLength = v.length();
}
//no child, the bone has the same direction as the parent bone.
if (bone.getChildren().isEmpty()) {
if (parent.getChildren().size() > 0) {
bGeom.setLocalRotation(parent.getChild(0).getLocalRotation());
} else {
//no parent, let's use the bind orientation of the bone
bGeom.setLocalRotation(bone.getBindRotation());
}
}
}
bGeom.setLocalScale(boneLength);
jGeom.setLocalScale(boneLength);
n.attachChild(bGeom);
n.attachChild(jGeom);
//tip
if (bone.getChildren().size() != 1) {
Geometry gt = jGeom.clone();
gt.scale(0.8f);
Vector3f v = new Vector3f(0, boneLength, 0);
if (guessBonesOrientation) {
if (bone.getChildren().isEmpty()) {
if (parent.getChildren().size() > 0) {
gt.setLocalTranslation(bGeom.getLocalRotation().mult(parent.getChild(0).getLocalRotation()).mult(v, v));
} else {
gt.setLocalTranslation(bGeom.getLocalRotation().mult(bone.getBindRotation()).mult(v, v));
}
}
} else {
gt.setLocalTranslation(v);
}
n.attachChild(gt);
}
boneNodes.put(bone, n);
nodeBones.put(n, bone);
parent.attachChild(n);
for (Bone childBone : bone.getChildren()) {
createSkeletonGeoms(childBone, boneShape, jointShape, boneLengths, skeleton, n, guessBonesOrientation);
}
}
protected Bone select(Geometry g) {
Node parentNode = g.getParent();
if (parent != null) {
Bone b = nodeBones.get(parentNode);
if (b != null) {
selectedNode = parentNode;
}
return b;
}
return null;
}
protected Node getSelectedNode() {
return selectedNode;
}
protected final void updateSkeletonGeoms(Bone bone) {
if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
return;
}
Node n = boneNodes.get(bone);
n.setLocalTranslation(bone.getLocalPosition());
n.setLocalRotation(bone.getLocalRotation());
n.setLocalScale(bone.getLocalScale());
for (Bone childBone : bone.getChildren()) {
updateSkeletonGeoms(childBone);
}
}
/**
* The method updates the geometry according to the positions of the bones.
*/
public void updateGeometry() {
for (Bone bone : skeleton.getRoots()) {
updateSkeletonGeoms(bone);
}
}
}

@ -0,0 +1,156 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.scene.debug.custom;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.collision.CollisionResults;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Nehon
*/
public class SkeletonDebugAppState extends AbstractAppState {
private Node debugNode = new Node("debugNode");
private Map<Skeleton, SkeletonDebugger> skeletons = new HashMap<Skeleton, SkeletonDebugger>();
private Map<Skeleton, Bone> selectedBones = new HashMap<Skeleton, Bone>();
private Application app;
@Override
public void initialize(AppStateManager stateManager, Application app) {
ViewPort vp = app.getRenderManager().createMainView("debug", app.getCamera());
vp.attachScene(debugNode);
vp.setClearDepth(true);
this.app = app;
for (SkeletonDebugger skeletonDebugger : skeletons.values()) {
skeletonDebugger.initialize(app.getAssetManager());
}
app.getInputManager().addListener(actionListener, "shoot");
app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
super.initialize(stateManager, app);
}
@Override
public void update(float tpf) {
debugNode.updateLogicalState(tpf);
debugNode.updateGeometricState();
}
public SkeletonDebugger addSkeleton(SkeletonControl skeletonControl, boolean guessBonesOrientation) {
Skeleton skeleton = skeletonControl.getSkeleton();
Spatial forSpatial = skeletonControl.getSpatial();
return addSkeleton(skeleton, forSpatial, guessBonesOrientation);
}
public SkeletonDebugger addSkeleton(Skeleton skeleton, Spatial forSpatial, boolean guessBonesOrientation) {
SkeletonDebugger sd = new SkeletonDebugger(forSpatial.getName() + "_Skeleton", skeleton, guessBonesOrientation);
sd.setLocalTransform(forSpatial.getWorldTransform());
if (forSpatial instanceof Node) {
List<Geometry> geoms = new ArrayList<>();
findGeoms((Node) forSpatial, geoms);
if (geoms.size() == 1) {
sd.setLocalTransform(geoms.get(0).getWorldTransform());
}
}
skeletons.put(skeleton, sd);
debugNode.attachChild(sd);
if (isInitialized()) {
sd.initialize(app.getAssetManager());
}
return sd;
}
private void findGeoms(Node node, List<Geometry> geoms) {
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
geoms.add((Geometry) spatial);
} else if (spatial instanceof Node) {
findGeoms((Node) spatial, geoms);
}
}
}
/**
* Pick a Target Using the Mouse Pointer. <ol><li>Map "pick target" action
* to a MouseButtonTrigger. <li>flyCam.setEnabled(false);
* <li>inputManager.setCursorVisible(true); <li>Implement action in
* AnalogListener (TODO).</ol>
*/
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("shoot") && isPressed) {
CollisionResults results = new CollisionResults();
Vector2f click2d = app.getInputManager().getCursorPosition();
Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d);
Ray ray = new Ray(click3d, dir);
debugNode.collideWith(ray, results);
if (results.size() > 0) {
// The closest result is the target that the player picked:
Geometry target = results.getClosestCollision().getGeometry();
for (SkeletonDebugger skeleton : skeletons.values()) {
Bone selectedBone = skeleton.select(target);
if (selectedBone != null) {
selectedBones.put(skeleton.getSkeleton(), selectedBone);
System.err.println("-----------------------");
System.err.println("Selected Bone : " + selectedBone.getName() + " in skeleton " + skeleton.getName());
System.err.println("Root Bone : " + (selectedBone.getParent() == null));
System.err.println("-----------------------");
System.err.println("Bind translation: " + selectedBone.getBindPosition());
System.err.println("Bind rotation: " + selectedBone.getBindRotation());
System.err.println("Bind scale: " + selectedBone.getBindScale());
System.err.println("---");
System.err.println("Local translation: " + selectedBone.getLocalPosition());
System.err.println("Local rotation: " + selectedBone.getLocalRotation());
System.err.println("Local scale: " + selectedBone.getLocalScale());
System.err.println("---");
System.err.println("Model translation: " + selectedBone.getModelSpacePosition());
System.err.println("Model rotation: " + selectedBone.getModelSpaceRotation());
System.err.println("Model scale: " + selectedBone.getModelSpaceScale());
System.err.println("---");
System.err.println("Bind inverse Transform: ");
System.err.println(selectedBone.getBindInverseTransform());
return;
}
}
}
}
}
};
public Map<Skeleton, Bone> getSelectedBones() {
return selectedBones;
}
public Node getDebugNode() {
return debugNode;
}
public void setDebugNode(Node debugNode) {
this.debugNode = debugNode;
}
}

@ -0,0 +1,218 @@
package com.jme3.scene.debug.custom;
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import com.jme3.animation.Bone;
import java.util.Map;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* The class that creates a mesh to display how bones behave. If it is supplied
* with the bones' lengths it will show exactly how the bones look like on the
* scene. If not then only connections between each bone heads will be shown.
*/
public class SkeletonDebugger extends BatchNode {
/**
* The lines of the bones or the wires between their heads.
*/
private SkeletonBone bones;
private Skeleton skeleton;
/**
* The dotted lines between a bone's tail and the had of its children. Not
* available if the length data was not provided.
*/
private SkeletonInterBoneWire interBoneWires;
private List<Bone> selectedBones = new ArrayList<Bone>();
public SkeletonDebugger() {
}
/**
* Creates a debugger with no length data. The wires will be a connection
* between the bones' heads only. The points will show the bones' heads only
* and no dotted line of inter bones connection will be visible.
*
* @param name the name of the debugger's node
* @param skeleton the skeleton that will be shown
*/
public SkeletonDebugger(String name, Skeleton skeleton, boolean guessBonesOrientation) {
super(name);
this.skeleton = skeleton;
skeleton.reset();
skeleton.updateWorldVectors();
Map<Integer, Float> boneLengths = new HashMap<Integer, Float>();
for (Bone bone : skeleton.getRoots()) {
computeLength(bone, boneLengths, skeleton);
}
bones = new SkeletonBone(skeleton, boneLengths, guessBonesOrientation);
this.attachChild(bones);
interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths, guessBonesOrientation);
Geometry g = new Geometry(name + "_interwires", interBoneWires);
g.setBatchHint(BatchHint.Never);
this.attachChild(g);
}
protected void initialize(AssetManager assetManager) {
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new ColorRGBA(0.05f, 0.05f, 0.05f, 1.0f));//new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f)
setMaterial(mat);
Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat2.setBoolean("VertexColor", true);
bones.setMaterial(mat2);
batch();
}
@Override
public final void setMaterial(Material material) {
if (batches.isEmpty()) {
for (int i = 0; i < children.size(); i++) {
children.get(i).setMaterial(material);
}
} else {
super.setMaterial(material);
}
}
public Skeleton getSkeleton() {
return skeleton;
}
private void computeLength(Bone b, Map<Integer, Float> boneLengths, Skeleton skeleton) {
if (b.getChildren().isEmpty()) {
if (b.getParent() != null) {
boneLengths.put(skeleton.getBoneIndex(b), boneLengths.get(skeleton.getBoneIndex(b.getParent())) * 0.75f);
} else {
boneLengths.put(skeleton.getBoneIndex(b), 0.1f);
}
} else {
float length = Float.MAX_VALUE;
for (Bone bone : b.getChildren()) {
float len = b.getModelSpacePosition().subtract(bone.getModelSpacePosition()).length();
if (len < length) {
length = len;
}
}
boneLengths.put(skeleton.getBoneIndex(b), length);
for (Bone bone : b.getChildren()) {
computeLength(bone, boneLengths, skeleton);
}
}
}
@Override
public void updateLogicalState(float tpf) {
super.updateLogicalState(tpf);
bones.updateGeometry();
if (interBoneWires != null) {
interBoneWires.updateGeometry();
}
}
ColorRGBA selectedColor = ColorRGBA.Orange;
ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f);
protected Bone select(Geometry g) {
Node oldNode = bones.getSelectedNode();
Bone b = bones.select(g);
if (b == null) {
return null;
}
if (oldNode != null) {
markSelected(oldNode, false);
}
markSelected(bones.getSelectedNode(), true);
return b;
}
/**
* @return the skeleton wires
*/
public SkeletonBone getBoneShapes() {
return bones;
}
/**
* @return the dotted line between bones (can be null)
*/
public SkeletonInterBoneWire getInterBoneWires() {
return interBoneWires;
}
protected void markSelected(Node n, boolean selected) {
ColorRGBA c = baseColor;
if (selected) {
c = selectedColor;
}
for (Spatial spatial : n.getChildren()) {
if (spatial instanceof Geometry) {
Geometry geom = (Geometry) spatial;
Geometry batch = (Geometry) getChild(getName() + "-batch0");
VertexBuffer vb = batch.getMesh().getBuffer(VertexBuffer.Type.Color);
FloatBuffer color = (FloatBuffer) vb.getData();
// System.err.println(getName() + "." + geom.getName() + " index " + getGeometryStartIndex(geom) * 4 + "/" + color.limit());
color.position(getGeometryStartIndex(geom) * 4);
for (int i = 0; i < geom.getVertexCount(); i++) {
color.put(c.r).put(c.g).put(c.b).put(c.a);
}
color.rewind();
vb.updateData(color);
}
}
}
}

@ -0,0 +1,141 @@
package com.jme3.scene.debug.custom;
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.nio.FloatBuffer;
import java.util.Map;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
/**
* A class that displays a dotted line between a bone tail and its childrens' heads.
*
* @author Marcin Roguski (Kaelthas)
*/
public class SkeletonInterBoneWire extends Mesh {
private static final int POINT_AMOUNT = 10;
/**
* The amount of connections between bones.
*/
private int connectionsAmount;
/**
* The skeleton that will be showed.
*/
private Skeleton skeleton;
/**
* The map between the bone index and its length.
*/
private Map<Integer, Float> boneLengths;
private boolean guessBonesOrientation = false;
/**
* Creates buffers for points. Each line has POINT_AMOUNT of points.
*
* @param skeleton the skeleton that will be showed
* @param boneLengths the lengths of the bones
*/
public SkeletonInterBoneWire(Skeleton skeleton, Map<Integer, Float> boneLengths, boolean guessBonesOrientation) {
this.skeleton = skeleton;
for (Bone bone : skeleton.getRoots()) {
this.countConnections(bone);
}
this.setMode(Mode.Points);
this.setPointSize(2);
this.boneLengths = boneLengths;
VertexBuffer pb = new VertexBuffer(Type.Position);
FloatBuffer fpb = BufferUtils.createFloatBuffer(POINT_AMOUNT * connectionsAmount * 3);
pb.setupData(Usage.Stream, 3, Format.Float, fpb);
this.setBuffer(pb);
this.guessBonesOrientation = guessBonesOrientation;
this.updateCounts();
}
/**
* The method updates the geometry according to the poitions of the bones.
*/
public void updateGeometry() {
VertexBuffer vb = this.getBuffer(Type.Position);
FloatBuffer posBuf = this.getFloatBuffer(Type.Position);
posBuf.clear();
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
Bone bone = skeleton.getBone(i);
Vector3f parentTail = bone.getModelSpacePosition().add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i))));
if (guessBonesOrientation) {
parentTail = bone.getModelSpacePosition();
}
for (Bone child : bone.getChildren()) {
Vector3f childHead = child.getModelSpacePosition();
Vector3f v = childHead.subtract(parentTail);
float pointDelta = v.length() / POINT_AMOUNT;
v.normalizeLocal().multLocal(pointDelta);
Vector3f pointPosition = parentTail.clone();
for (int j = 0; j < POINT_AMOUNT; ++j) {
posBuf.put(pointPosition.getX()).put(pointPosition.getY()).put(pointPosition.getZ());
pointPosition.addLocal(v);
}
}
}
posBuf.flip();
vb.updateData(posBuf);
this.updateBound();
}
/**
* Th method couns the connections between bones.
*
* @param bone the bone where counting starts
*/
private void countConnections(Bone bone) {
for (Bone child : bone.getChildren()) {
++connectionsAmount;
this.countConnections(child);
}
}
}

@ -31,21 +31,18 @@
*/ */
package com.jme3.scene.instancing; package com.jme3.scene.instancing;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.MatParam;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry; import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.GeometryGroupNode; import com.jme3.scene.*;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.UserData;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.MatParam;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -217,6 +214,7 @@ public class InstancedNode extends GeometryGroupNode {
ig.setMesh(lookUp.mesh); ig.setMesh(lookUp.mesh);
ig.setUserData(UserData.JME_PHYSICSIGNORE, true); ig.setUserData(UserData.JME_PHYSICSIGNORE, true);
ig.setCullHint(CullHint.Never); ig.setCullHint(CullHint.Never);
ig.setShadowMode(RenderQueue.ShadowMode.Inherit);
instancesMap.put(lookUp.clone(), ig); instancesMap.put(lookUp.clone(), ig);
attachChild(ig); attachChild(ig);
} }

@ -76,6 +76,28 @@ public abstract class IndexBuffer {
} }
} }
/**
* @see Buffer#rewind()
*/
public void rewind() {
getBuffer().rewind();
}
/**
* @return
* @see Buffer#remaining()
*/
public int remaining() {
return getBuffer().remaining();
}
/**
* Returns the vertex index for the current position.
*
* @return
*/
public abstract int get();
/** /**
* Returns the vertex index for the given index in the index buffer. * Returns the vertex index for the given index in the index buffer.
* *

@ -48,6 +48,11 @@ public class IndexByteBuffer extends IndexBuffer {
buf.rewind(); buf.rewind();
} }
@Override
public int get() {
return buf.get() & 0x000000FF;
}
@Override @Override
public int get(int i) { public int get(int i) {
return buf.get(i) & 0x000000FF; return buf.get(i) & 0x000000FF;

@ -48,6 +48,10 @@ public class IndexIntBuffer extends IndexBuffer {
buf.rewind(); buf.rewind();
} }
@Override
public int get() {
return buf.get();
}
@Override @Override
public int get(int i) { public int get(int i) {
return buf.get(i); return buf.get(i);

@ -48,6 +48,10 @@ public class IndexShortBuffer extends IndexBuffer {
buf.rewind(); buf.rewind();
} }
@Override
public int get() {
return buf.get() & 0x0000FFFF;
}
@Override @Override
public int get(int i) { public int get(int i) {
return buf.get(i) & 0x0000FFFF; return buf.get(i) & 0x0000FFFF;

@ -55,6 +55,7 @@ public class VirtualIndexBuffer extends IndexBuffer {
protected int numVerts = 0; protected int numVerts = 0;
protected int numIndices = 0; protected int numIndices = 0;
protected Mode meshMode; protected Mode meshMode;
protected int position = 0;
public VirtualIndexBuffer(int numVerts, Mode meshMode){ public VirtualIndexBuffer(int numVerts, Mode meshMode){
this.numVerts = numVerts; this.numVerts = numVerts;
@ -86,6 +87,23 @@ public class VirtualIndexBuffer extends IndexBuffer {
} }
} }
@Override
public int get() {
int i = get(position);
position++;
return i;
}
@Override
public void rewind() {
position = 0;
}
@Override
public int remaining() {
return numIndices - position;
}
@Override @Override
public int get(int i) { public int get(int i) {
if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){ if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){

@ -50,7 +50,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* the indentation characters tabulation characters * the indentation characters tabulation characters
*/ */
private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
private ShaderNodeVariable inPosTmp; protected ShaderNodeVariable inPosTmp;
/** /**
* creates a Glsl100ShaderGenerator * creates a Glsl100ShaderGenerator
@ -110,7 +110,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
protected void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { protected void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type) {
source.append("\n"); source.append("\n");
for (ShaderNodeVariable var : info.getVaryings()) { for (ShaderNodeVariable var : info.getVaryings()) {
declareVarying(source, var, type == ShaderType.Vertex ? false : true); declareVarying(source, var, type != ShaderType.Vertex);
} }
} }
@ -211,7 +211,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* this methods does things in this order : * this methods does things in this order :
* *
* 1. declaring and mapping input<br> * 1. declaring and mapping input<br>
* variables : variable replaced with MatParams or WorldParams are not * variables : variable replaced with MatParams or WorldParams that are Samplers are not
* declared and are replaced by the param actual name in the code. For others * declared and are replaced by the param actual name in the code. For others
* variables, the name space is appended with a "_" before the variable name * variables, the name space is appended with a "_" before the variable name
* in the code to avoid names collision between shaderNodes. <br> * in the code to avoid names collision between shaderNodes. <br>
@ -237,27 +237,59 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
comment(source, shaderNode, "Begin"); comment(source, shaderNode, "Begin");
startCondition(shaderNode.getCondition(), source); startCondition(shaderNode.getCondition(), source);
List<String> declaredInputs = new ArrayList<String>(); final List<String> declaredInputs = new ArrayList<>();
for (VariableMapping mapping : shaderNode.getInputMapping()) { for (VariableMapping mapping : shaderNode.getInputMapping()) {
//all variables fed with a matparam or world param are replaced but the matparam itself final ShaderNodeVariable rightVariable = mapping.getRightVariable();
//it avoids issue with samplers that have to be uniforms, and it optimize a but the shader code. final ShaderNodeVariable leftVariable = mapping.getLeftVariable();
if (isWorldOrMaterialParam(mapping.getRightVariable())) {
nodeSource = replace(nodeSource, mapping.getLeftVariable(), mapping.getRightVariable().getPrefix() + mapping.getRightVariable().getName()); //Variables fed with a sampler matparam or world param are replaced by the matparam itself
//It avoids issue with samplers that have to be uniforms.
if (isWorldOrMaterialParam(rightVariable) && rightVariable.getType().startsWith("sampler")) {
nodeSource = replace(nodeSource, leftVariable, rightVariable.getPrefix() + rightVariable.getName());
} else { } else {
if (mapping.getLeftVariable().getType().startsWith("sampler")) {
if (leftVariable.getType().startsWith("sampler")) {
throw new IllegalArgumentException("a Sampler must be a uniform"); throw new IllegalArgumentException("a Sampler must be a uniform");
} }
map(mapping, source); map(mapping, source);
String newName = shaderNode.getName() + "_" + mapping.getLeftVariable().getName(); }
String newName = shaderNode.getName() + "_" + leftVariable.getName();
if (!declaredInputs.contains(newName)) { if (!declaredInputs.contains(newName)) {
nodeSource = replace(nodeSource, mapping.getLeftVariable(), newName); nodeSource = replace(nodeSource, leftVariable, newName);
declaredInputs.add(newName); declaredInputs.add(newName);
} }
} }
final ShaderNodeDefinition definition = shaderNode.getDefinition();
for (final ShaderNodeVariable var : definition.getInputs()) {
if (var.getDefaultValue() == null) {
continue;
}
final String fullName = shaderNode.getName() + "_" + var.getName();
if (declaredInputs.contains(fullName)) {
continue;
}
final ShaderNodeVariable variable = new ShaderNodeVariable(var.getType(), shaderNode.getName(),
var.getName(), var.getMultiplicity());
if (!isVarying(info, variable)) {
declareVariable(source, variable, var.getDefaultValue(), true, null);
}
nodeSource = replaceVariableName(nodeSource, variable);
declaredInputs.add(fullName);
} }
for (ShaderNodeVariable var : shaderNode.getDefinition().getOutputs()) { for (ShaderNodeVariable var : definition.getOutputs()) {
ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName(), var.getMultiplicity()); ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName(), var.getMultiplicity());
if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) { if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) {
if (!isVarying(info, v)) { if (!isVarying(info, v)) {
@ -421,6 +453,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
source.append(" = "); source.append(" = ");
String namePrefix = getAppendableNameSpace(mapping.getRightVariable()); String namePrefix = getAppendableNameSpace(mapping.getRightVariable());
source.append(namePrefix); source.append(namePrefix);
source.append(mapping.getRightVariable().getPrefix());
source.append(mapping.getRightVariable().getName()); source.append(mapping.getRightVariable().getName());
if (mapping.getRightSwizzling().length() > 0) { if (mapping.getRightSwizzling().length() > 0) {
source.append("."); source.append(".");
@ -602,7 +635,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* makes sure inPosition attribute is of type vec3 or vec4 * makes sure inPosition attribute is of type vec3 or vec4
* @param var the inPosition attribute * @param var the inPosition attribute
*/ */
private void fixInPositionType(ShaderNodeVariable var) { protected void fixInPositionType(ShaderNodeVariable var) {
if(!var.getType().equals("vec3") || !var.getType().equals("vec4")){ if(!var.getType().equals("vec3") || !var.getType().equals("vec4")){
var.setType("vec3"); var.setType("vec3");
} }

@ -35,6 +35,8 @@ import com.jme3.asset.AssetManager;
import com.jme3.material.ShaderGenerationInfo; import com.jme3.material.ShaderGenerationInfo;
import com.jme3.shader.Shader.ShaderType; import com.jme3.shader.Shader.ShaderType;
import java.util.List;
/** /**
* This shader Generator can generate Vertex and Fragment shaders from * This shader Generator can generate Vertex and Fragment shaders from
* ShaderNodes for GLSL 1.5 * ShaderNodes for GLSL 1.5
@ -78,6 +80,25 @@ public class Glsl150ShaderGenerator extends Glsl100ShaderGenerator {
declareVariable(source, var, true, input ? "in" : "out"); declareVariable(source, var, true, input ? "in" : "out");
} }
@Override
protected void generateUniforms(StringBuilder source, ShaderGenerationInfo info, ShaderType type) {
generateCompatibilityDefines(source, type);
super.generateUniforms(source, info, type);
}
private void generateCompatibilityDefines(StringBuilder source, ShaderType type) {
//Adding compatibility defines, as it's more efficient than replacing the function calls in the source code
if (type == ShaderType.Fragment) {
source.append("\n")
.append("#define texture1D texture\n")
.append("#define texture2D texture\n")
.append("#define texture3D texture\n")
.append("#define textureCube texture\n")
.append("#define texture2DLod textureLod\n")
.append("#define textureCubeLod textureLod\n");
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save