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. 14
      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. 34
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  16. 37
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  17. 133
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java
  18. 24
      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. 138
      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. 165
      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. 734
      jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
  35. 20
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  36. 10
      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. 144
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  40. 56
      jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java
  41. 5
      jme3-core/src/main/java/com/jme3/asset/ImplHandler.java
  42. 1435
      jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java
  43. 182
      jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java
  44. 35
      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. 982
      jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
  47. 459
      jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java
  48. 2
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  49. 43
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  50. 84
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  51. 176
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  52. 117
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java
  53. 292
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  54. 34
      jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java
  55. 314
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  56. 40
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  57. 16
      jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
  58. 256
      jme3-core/src/main/java/com/jme3/input/FlyByCamera.java
  59. 72
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  60. 20
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  61. 13
      jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java
  62. 22
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  63. 13
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  64. 17
      jme3-core/src/main/java/com/jme3/math/Transform.java
  65. 16
      jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java
  66. 7
      jme3-core/src/main/java/com/jme3/post/Filter.java
  67. 25
      jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
  68. 22
      jme3-core/src/main/java/com/jme3/renderer/Camera.java
  69. 52
      jme3-core/src/main/java/com/jme3/renderer/Caps.java
  70. 3
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  71. 2
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java
  72. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
  73. 6
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
  74. 7
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
  75. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
  76. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
  77. 33
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  78. 127
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  79. 24
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
  80. 11
      jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java
  81. 30
      jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
  82. 231
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  83. 245
      jme3-core/src/main/java/com/jme3/scene/CameraNode.java
  84. 3
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  85. 149
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  86. 53
      jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
  87. 5
      jme3-core/src/main/java/com/jme3/scene/debug/Grid.java
  88. 20
      jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java
  89. 417
      jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java
  90. 238
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java
  91. 156
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java
  92. 218
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java
  93. 141
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java
  94. 16
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  95. 24
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java
  96. 7
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java
  97. 4
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java
  98. 4
      jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java
  99. 18
      jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java
  100. 69
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.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
**/.settings
**/.project
**/out
/.gradle/
/.nb-gradle/
/.idea/

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

@ -23,7 +23,10 @@ environment:
secure: Ek2lqC2e19qQDRRdlvnYyLFBq3TNj6YwKTAPuJ2VElJsxi9lQg+9ZP+VbP4kbHTx6Zaa++vtmOuxLZL7gdILrEEPa1Jix2BBLBfcxBUxe6w=
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:
- cmd: gradlew.bat -PbuildNativeProjects=true :jme3-bullet-native:assemble
@ -40,6 +43,10 @@ on_success:
- 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
git config --global user.email "appveyor"
git config --global user.name "appveyor"
git checkout -q %APPVEYOR_REPO_BRANCH%
git add -- jme3-bullet-native/libs/native/windows/

@ -15,7 +15,7 @@ apply from: file('version.gradle')
subprojects {
if(!project.name.equals('jme3-android-examples')) {
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')
}
} else {
@ -66,9 +66,9 @@ task createZipDistribution(type:Zip,dependsOn:["dist","libDist"], description:"P
archiveName "jME" + jmeFullVersion + ".zip"
into("/") {
from {"./dist"}
}
}
into("/sources") {
from {"$buildDir/libDist/sources"}
from {"$buildDir/libDist/sources"}
}
}
@ -88,14 +88,14 @@ task dist(dependsOn: [':jme3-examples:dist', 'mergedJavadoc']){
task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the projects.') {
title = 'jMonkeyEngine3'
destinationDir = mkdir("dist/javadoc")
options.encoding = 'UTF-8'
// Allows Javadoc to be generated on Java 8 despite doclint errors.
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
options.overview = file("javadoc-overview.html")
// Note: The closures below are executed lazily.
source subprojects.collect {project ->
@ -116,7 +116,7 @@ task mergedSource(type: Copy){
}
task wrapper(type: Wrapper, description: 'Creates and deploys the Gradle wrapper to the current directory.') {
gradleVersion = '3.2.1'
gradleVersion = '4.1'
}
ext {
@ -137,7 +137,7 @@ task configureAndroidNDK {
if (System.env.ANDROID_NDK != null) {
ndkBuildPath = System.env.ANDROID_NDK + File.separator + ndkBuildFile
}
if (new File(ndkBuildPath).exists()) {
ndkExists = true
ndkCommandPath = ndkBuildPath

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

@ -19,8 +19,8 @@ buildAndroidExamples = false
ndkPath = /opt/android-ndk-r10c
# Path for downloading native Bullet
bulletUrl = https://github.com/bulletphysics/bullet3/archive/2.83.7.zip
bulletFolder = bullet3-2.83.7
bulletUrl = https://github.com/bulletphysics/bullet3/archive/2.86.1.zip
bulletFolder = bullet3-2.86.1
bulletZipFile = bullet3.zip
# 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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save ( ) {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
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
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line

@ -5,5 +5,5 @@ if (!hasProperty('mainClass')) {
dependencies {
compile project(':jme3-core')
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 =
((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
((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}",
// new Object[]{source, isTouch});
consumed = touchInput.onKey(event);

@ -561,4 +561,9 @@ public class AndroidGL implements GL, GLExt, GLFbo {
public void glBlendEquationSeparate(int colorMode, int 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.renderer.android.AndroidGL;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLDebugES;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.opengl.GLTracer;
import com.jme3.system.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
@ -195,8 +197,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
timer = new NanoTimer();
Object gl = new AndroidGL();
// gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl);
// gl = new GLDebugES((GL)gl, (GLExt)gl);
if (settings.getBoolean("GraphicsDebug")) {
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.initialize();

@ -1,6 +1,7 @@
package com.jme3.scene.plugins.blender.materials;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -163,6 +164,12 @@ public final class MaterialContext implements Savable {
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
int textureIndex = 0;
@ -175,16 +182,19 @@ public final class MaterialContext implements Savable {
String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture());
List<Vector2f> uvs = combinedTexture.getResultUVS();
if(uvs != null && uvs.size() > 0) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
}//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file)
if(usedUserUVSet != null) {
userDefinedUVCoordinates = new HashMap<>(userDefinedUVCoordinates);
userDefinedUVCoordinates.remove(usedUserUVSet);
if(usedUserUVSet == null || unusedUserDefinedUVCoords.containsKey(usedUserUVSet)) {
List<Vector2f> uvs = combinedTexture.getResultUVS();
if(uvs != null && uvs.size() > 0) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
}//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) {
unusedUserDefinedUVCoords.remove(usedUserUVSet);
}
}
} 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);
@ -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.");
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);
}
for (Entry<String, List<Vector2f>> entry : userDefinedUVCoordinates.entrySet()) {
for (Entry<String, List<Vector2f>> entry : unusedUserDefinedUVCoords.entrySet()) {
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
List<Vector2f> uvs = entry.getValue();
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);

@ -291,27 +291,30 @@ public class MeshHelper extends AbstractBlenderHelper {
Structure defbase = (Structure) parent.getFieldValue("defbase");
List<String> groupNames = new ArrayList<String>();
List<Structure> defs = defbase.evaluateListBase();
for (Structure def : defs) {
groupNames.add(def.getFieldValue("name").toString());
}
if(!defs.isEmpty()) {
for (Structure def : defs) {
groupNames.add(def.getFieldValue("name").toString());
}
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData();
for (Structure dvert : dverts) {
Map<String, Float> weightsForVertex = new HashMap<String, Float>();
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if (pDW.isNotNull()) {
List<Structure> dw = pDW.fetchData();
for (Structure deformWeight : dw) {
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
String groupName = groupNames.get(groupIndex);
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData();
for (Structure dvert : dverts) {
Map<String, Float> weightsForVertex = new HashMap<String, Float>();
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if (pDW.isNotNull()) {
List<Structure> dw = pDW.fetchData();
for (Structure deformWeight : dw) {
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
String groupName = groupNames.get(groupIndex);
weightsForVertex.put(groupName, weight);
weightsForVertex.put(groupName, weight);
}
}
result.add(weightsForVertex);
}
result.add(weightsForVertex);
}
}
}

@ -31,12 +31,14 @@
*/
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.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.plugins.AWTLoader;
import com.jme3.texture.plugins.DDSLoader;
import com.jme3.texture.plugins.TGALoader;
import java.io.InputStream;
import com.jme3.texture.plugins.HDRLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@ -47,11 +49,29 @@ import java.util.logging.Logger;
*/
/* package */class ImageLoader extends AWTLoader {
private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName());
protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader
private static final Logger hdrLogger = Logger.getLogger(HDRLoader.class.getName()); // Used to silence HDR Errors
/**
* 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
* blender input stream
@ -60,76 +80,57 @@ import java.util.logging.Logger;
* @param flipY
* if the image should be flipped (does not work with DirectX image)
* @return loaded image or null if it could not be loaded
* @deprecated This method has only been left in for API compability.
* Use loadTexture instead
*/
public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) {
// loading using AWT loader
inputStream.setPosition(startPosition);
Image result = this.loadImage(inputStream, ImageType.AWT, flipY);
// loading using TGA loader
if (result == null) {
inputStream.setPosition(startPosition);
result = this.loadImage(inputStream, ImageType.TGA, flipY);
}
// loading using DDS loader
if (result == null) {
inputStream.setPosition(startPosition);
result = this.loadImage(inputStream, ImageType.DDS, flipY);
public Image loadImage(AssetManager assetManager, BlenderInputStream inputStream, int startPosition, boolean flipY) {
Texture tex = loadTexture(assetManager, inputStream, startPosition, flipY);
if (tex == null) {
return null;
} else {
return tex.getImage();
}
if (result == null) {
LOGGER.warning("Image could not be loaded by none of available loaders!");
}
return result;
}
/**
* This method loads an image of a specified type from the given input stream.
* This method loads a texture which is packed into the blender file.
* It makes use of all the registered AssetLoaders
*
* @param inputStream
* the input stream we read the image from
* @param imageType
* the type of the image {@link ImageType}
* blender input stream
* @param startPosition
* position in the stream where the image data starts
* @param flipY
* if the image should be flipped (does not work with DirectX image)
* @return loaded image or null if it could not be loaded
* @return loaded texture or null if it could not be loaded
*/
public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) {
Image result = null;
switch (imageType) {
case AWT:
try {
result = this.load(inputStream, flipY);
} catch (Exception e) {
LOGGER.warning("Unable to load image using AWT loader!");
}
break;
case DDS:
try {
result = ddsLoader.load(inputStream);
} catch (Exception e) {
LOGGER.warning("Unable to load image using DDS loader!");
}
break;
case TGA:
try {
result = TGALoader.load(inputStream, flipY);
} catch (Exception e) {
LOGGER.warning("Unable to load image using TGA loader!");
}
break;
default:
throw new IllegalStateException("Unknown image type: " + imageType);
public Texture loadTexture(AssetManager assetManager, BlenderInputStream inputStream, int startPosition, boolean flipY) {
inputStream.setPosition(startPosition);
TextureKey tKey;
Texture result = null;
hdrLogger.setLevel(Level.SEVERE); // When we bruteforce try HDR on a non hdr file, it prints unreadable chars
for (String ext: extensions) {
tKey = new TextureKey("dummy" + ext, flipY);
try {
result = assetManager.loadAssetFromStream(tKey, inputStream);
} catch (Exception e) {
continue;
}
if (result != null) {
break; // Could locate a possible asset
}
}
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.image.ColorSpace;
import com.jme3.util.BufferUtils;
import com.jme3.util.PlaceholderAssets;
/**
* A class that is used in texture calculations.
@ -251,7 +252,11 @@ public class TextureHelper extends AbstractBlenderHelper {
blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());
// 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 {
@ -614,8 +619,15 @@ public class TextureHelper extends AbstractBlenderHelper {
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
boolean negateTexture = (texflag & 0x04) != 0;
boolean colorSet = false;
for (int i = 0; i < mappings.length; ++i) {
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);
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() };
@ -646,8 +658,16 @@ public class TextureHelper extends AbstractBlenderHelper {
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
for (TextureData data : textures) {
Number mapto = (Number) data.mtex.getFieldValue("mapto");
boolean colorSet = false;
for (int i = 0; i < mappings.length; ++i) {
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]);
if (datas == null) {
datas = new ArrayList<TextureData>();
@ -668,4 +688,4 @@ public class TextureHelper extends AbstractBlenderHelper {
/** The name of the user's UV coordinates that are used for this texture. */
public String uvCoordinatesName;
}
}
}

@ -163,9 +163,19 @@ public class TextureBlenderAWT extends AbstractTextureBlender {
* the blender context
*/
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 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) {
case MTEX_BLEND:
result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];

@ -209,7 +209,7 @@ JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_
env->ThrowNew(newExc, "The manifoldPoint does not exist.");
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
* Method: setUpAxis
* Signature: (JI)V
* Method: setUp
* 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_setAngularVelocity
(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->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_setUpAxis
(JNIEnv *env, jobject object, jlong objectId, jint value) {
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;
}
character->setUpAxis(value);
btVector3 l_vel = character->getLinearVelocity();
jmeBulletUtil::convert(env, &l_vel, value);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setFallSpeed
@ -178,25 +258,130 @@ extern "C" {
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setGravity
* Signature: (JF)V
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
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);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
return;
}
character->setGravity(value);
btVector3 vec = btVector3();
jmeBulletUtil::convert(env, value, &vec);
character->setGravity(vec);
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* 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
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearDamping
(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->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_getGravity
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) {
@ -204,9 +389,10 @@ extern "C" {
env->ThrowNew(newExc, "The native object does not exist.");
return 0;
}
return character->getGravity();
return character->getStepHeight();
}
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setMaxSlope
@ -239,6 +425,39 @@ extern "C" {
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
* Method: onGround
@ -258,17 +477,37 @@ extern "C" {
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: jump
* Signature: (J)V
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
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);
if (character == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The native object does not exist.");
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
* Method: setUpAxis
* Signature: (JI)V
* Method: setUp
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis
(JNIEnv *, jobject, jlong, jint);
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_getLinearVelocity
(JNIEnv *, jobject , jlong , jobject );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
@ -108,18 +144,72 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpee
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: setGravity
* Signature: (JF)V
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity
(JNIEnv *, jobject, jlong, jfloat);
(JNIEnv *, jobject, jlong, jobject);
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: getGravity
* Signature: (J)F
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity
(JNIEnv *, jobject, jlong);
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
*/
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getStepHeight
(JNIEnv *, jobject , jlong );
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
@ -137,6 +227,24 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope
JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope
(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
* Method: onGround
@ -148,10 +256,18 @@ JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGroun
/*
* Class: com_jme3_bullet_objects_PhysicsCharacter
* Method: jump
* Signature: (J)V
* Signature: (JLcom/jme3/math/Vector3f;)V
*/
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

@ -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
* Method: getPhysicsLocation
@ -217,7 +252,8 @@ extern "C" {
body->setActivationState(DISABLE_DEACTIVATION);
} else {
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
(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
* Method: getPhysicsRotation

@ -127,13 +127,65 @@ public class PhysicsCharacter extends PhysicsCollisionObject {
public Vector3f getWalkDirection() {
return walkDirection;
}
/**
* @deprecated Deprecated in bullet 2.86.1 use setUp(Vector3f) instead
*/
@Deprecated
public void setUpAxis(int axis) {
if(axis<0) axis=0;
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 setUpAxis(int axis) {
upAxis = axis;
setUpAxis(characterId, axis);
public void setLinearVelocity(Vector3f v){
setLinearVelocity(characterId,v);
}
private native void setLinearVelocity(long characterId, Vector3f v);
private native void setUpAxis(long characterId, int axis);
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() {
return upAxis;
@ -161,18 +213,96 @@ public class PhysicsCharacter extends PhysicsCollisionObject {
return jumpSpeed;
}
/**
* @deprecated Deprecated in bullet 2.86.1. Use setGravity(Vector3f) instead.
*/
@Deprecated
public void setGravity(float value) {
setGravity(new Vector3f(0,value,0));
}
public void setGravity(Vector3f 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() {
return getGravity(characterId);
}
private native float getGravity(long 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 void setMaxPenetrationDepth(long characterId,float v);
public void setMaxSlope(float slopeRadians) {
setMaxSlope(characterId, slopeRadians);
}
@ -191,11 +321,20 @@ public class PhysicsCharacter extends PhysicsCollisionObject {
private native boolean onGround(long characterId);
/**
* @deprecated Deprecated in bullet 2.86.1. Use jump(Vector3f) instead.
*/
@Deprecated
public void jump() {
jump(characterId);
jump(Vector3f.UNIT_Y);
}
private native void jump(long characterId);
public void jump(Vector3f dir) {
jump(characterId,dir);
}
private native void jump(long characterId,Vector3f v);
@Override
public void setCollisionShape(CollisionShape collisionShape) {

@ -185,6 +185,21 @@ public class PhysicsRigidBody extends PhysicsCollisionObject {
getPhysicsRotation(objectId, 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);
@ -352,7 +367,6 @@ public class PhysicsRigidBody extends PhysicsCollisionObject {
public void setGravity(Vector3f gravity) {
setGravity(objectId, gravity);
}
private native void setGravity(long objectId, Vector3f gravity);
public float getFriction() {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved.
*
* 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.infos.ChildCollisionShape;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
@ -111,9 +112,16 @@ public class DebugShapeFactory {
public static Mesh getDebugMesh(CollisionShape shape) {
Mesh mesh = new Mesh();
mesh = new Mesh();
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);
shape.setScale(savedScale);
mesh.setBuffer(Type.Position, 3, callback.getVertices());
mesh.getFloatBuffer(Type.Position).clear();
return mesh;

@ -1,365 +1,369 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.animation;
import com.jme3.math.FastMath;
import com.jme3.util.TempVars;
import java.util.BitSet;
/**
* <code>AnimChannel</code> provides controls, such as play, pause,
* fast forward, etc, for an animation. The animation
* channel may influence the entire model or specific bones of the model's
* skeleton. A single model may have multiple animation channels influencing
* various parts of its body. For example, a character model may have an
* animation channel for its feet, and another for its torso, and
* the animations for each channel are controlled independently.
*
* @author Kirill Vainer
*/
public final class AnimChannel {
private static final float DEFAULT_BLEND_TIME = 0.15f;
private AnimControl control;
private BitSet affectedBones;
private Animation animation;
private Animation blendFrom;
private float time;
private float speed;
private float timeBlendFrom;
private float blendTime;
private float speedBlendFrom;
private boolean notified=false;
private LoopMode loopMode, loopModeBlendFrom;
private float blendAmount = 1f;
private float blendRate = 0;
AnimChannel(AnimControl control){
this.control = control;
}
/**
* Returns the parent control of this AnimChannel.
*
* @return the parent control of this AnimChannel.
* @see AnimControl
*/
public AnimControl getControl() {
return control;
}
/**
* @return The name of the currently playing animation, or null if
* none is assigned.
*
* @see AnimChannel#setAnim(java.lang.String)
*/
public String getAnimationName() {
return animation != null ? animation.getName() : null;
}
/**
* @return The loop mode currently set for the animation. The loop mode
* determines what will happen to the animation once it finishes
* playing.
*
* For more information, see the LoopMode enum class.
* @see LoopMode
* @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode)
*/
public LoopMode getLoopMode() {
return loopMode;
}
/**
* @param loopMode Set the loop mode for the channel. The loop mode
* determines what will happen to the animation once it finishes
* playing.
*
* For more information, see the LoopMode enum class.
* @see LoopMode
*/
public void setLoopMode(LoopMode loopMode) {
this.loopMode = loopMode;
}
/**
* @return The speed that is assigned to the animation channel. The speed
* is a scale value starting from 0.0, at 1.0 the animation will play
* at its default speed.
*
* @see AnimChannel#setSpeed(float)
*/
public float getSpeed() {
return speed;
}
/**
* @param speed Set the speed of the animation channel. The speed
* is a scale value starting from 0.0, at 1.0 the animation will play
* at its default speed.
*/
public void setSpeed(float speed) {
this.speed = speed;
if(blendTime>0){
this.speedBlendFrom = speed;
blendTime = Math.min(blendTime, animation.getLength() / speed);
blendRate = 1/ blendTime;
}
}
/**
* @return The time of the currently playing animation. The time
* starts at 0 and continues on until getAnimMaxTime().
*
* @see AnimChannel#setTime(float)
*/
public float getTime() {
return time;
}
/**
* @param time Set the time of the currently playing animation, the time
* is clamped from 0 to {@link #getAnimMaxTime()}.
*/
public void setTime(float time) {
this.time = FastMath.clamp(time, 0, getAnimMaxTime());
}
/**
* @return The length of the currently playing animation, or zero
* if no animation is playing.
*
* @see AnimChannel#getTime()
*/
public float getAnimMaxTime(){
return animation != null ? animation.getLength() : 0f;
}
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* This resets the time to zero, and optionally blends the animation
* over <code>blendTime</code> seconds with the currently playing animation.
* Notice that this method will reset the control's speed to 1.0.
*
* @param name The name of the animation to play
* @param blendTime The blend time over which to blend the new animation
* with the old one. If zero, then no blending will occur and the new
* animation will be applied instantly.
*/
public void setAnim(String name, float blendTime){
if (name == null)
throw new IllegalArgumentException("name cannot be null");
if (blendTime < 0f)
throw new IllegalArgumentException("blendTime cannot be less than zero");
Animation anim = control.animationMap.get(name);
if (anim == null)
throw new IllegalArgumentException("Cannot find animation named: '"+name+"'");
control.notifyAnimChange(this, name);
if (animation != null && blendTime > 0f){
this.blendTime = blendTime;
// activate blending
blendTime = Math.min(blendTime, anim.getLength() / speed);
blendFrom = animation;
timeBlendFrom = time;
speedBlendFrom = speed;
loopModeBlendFrom = loopMode;
blendAmount = 0f;
blendRate = 1f / blendTime;
}else{
blendFrom = null;
}
animation = anim;
time = 0;
speed = 1f;
loopMode = LoopMode.Loop;
notified = false;
}
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* See {@link #setAnim(java.lang.String, float)}.
* The blendTime argument by default is 150 milliseconds.
*
* @param name The name of the animation to play
*/
public void setAnim(String name){
setAnim(name, DEFAULT_BLEND_TIME);
}
/**
* Add all the bones of the model's skeleton to be
* influenced by this animation channel.
*/
public void addAllBones() {
affectedBones = null;
}
/**
* Add a single bone to be influenced by this animation channel.
*/
public void addBone(String name) {
addBone(control.getSkeleton().getBone(name));
}
/**
* Add a single bone to be influenced by this animation channel.
*/
public void addBone(Bone bone) {
int boneIndex = control.getSkeleton().getBoneIndex(bone);
if(affectedBones == null) {
affectedBones = new BitSet(control.getSkeleton().getBoneCount());
}
affectedBones.set(boneIndex);
}
/**
* Add bones to be influenced by this animation channel starting from the
* given bone name and going toward the root bone.
*/
public void addToRootBone(String name) {
addToRootBone(control.getSkeleton().getBone(name));
}
/**
* Add bones to be influenced by this animation channel starting from the
* given bone and going toward the root bone.
*/
public void addToRootBone(Bone bone) {
addBone(bone);
while (bone.getParent() != null) {
bone = bone.getParent();
addBone(bone);
}
}
/**
* Add bones to be influenced by this animation channel, starting
* from the given named bone and going toward its children.
*/
public void addFromRootBone(String name) {
addFromRootBone(control.getSkeleton().getBone(name));
}
/**
* Add bones to be influenced by this animation channel, starting
* from the given bone and going toward its children.
*/
public void addFromRootBone(Bone bone) {
addBone(bone);
if (bone.getChildren() == null)
return;
for (Bone childBone : bone.getChildren()) {
addBone(childBone);
addFromRootBone(childBone);
}
}
BitSet getAffectedBones(){
return affectedBones;
}
public void reset(boolean rewind){
if(rewind){
setTime(0);
if(control.getSkeleton()!=null){
control.getSkeleton().resetAndUpdate();
}else{
TempVars vars = TempVars.get();
update(0, vars);
vars.release();
}
}
animation = null;
notified = false;
}
void update(float tpf, TempVars vars) {
if (animation == null)
return;
if (blendFrom != null && blendAmount != 1.0f){
// The blendFrom anim is set, the actual animation
// playing will be set
// blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
timeBlendFrom += tpf * speedBlendFrom;
timeBlendFrom = AnimationUtils.clampWrapTime(timeBlendFrom,
blendFrom.getLength(),
loopModeBlendFrom);
if (timeBlendFrom < 0){
timeBlendFrom = -timeBlendFrom;
speedBlendFrom = -speedBlendFrom;
}
blendAmount += tpf * blendRate;
if (blendAmount > 1f){
blendAmount = 1f;
blendFrom = null;
}
}
animation.setTime(time, blendAmount, control, this, vars);
time += tpf * speed;
if (animation.getLength() > 0){
if (!notified && (time >= animation.getLength() || time < 0)) {
if (loopMode == LoopMode.DontLoop) {
// Note that this flag has to be set before calling the notify
// since the notify may start a new animation and then unset
// the flag.
notified = true;
}
control.notifyAnimCycleDone(this, animation.getName());
}
}
time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode);
if (time < 0){
// Negative time indicates that speed should be inverted
// (for cycle loop mode only)
time = -time;
speed = -speed;
}
}
}
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.animation;
import com.jme3.math.FastMath;
import com.jme3.util.TempVars;
import java.util.BitSet;
/**
* <code>AnimChannel</code> provides controls, such as play, pause,
* fast forward, etc, for an animation. The animation
* channel may influence the entire model or specific bones of the model's
* skeleton. A single model may have multiple animation channels influencing
* various parts of its body. For example, a character model may have an
* animation channel for its feet, and another for its torso, and
* the animations for each channel are controlled independently.
*
* @author Kirill Vainer
*/
public final class AnimChannel {
private static final float DEFAULT_BLEND_TIME = 0.15f;
private AnimControl control;
private BitSet affectedBones;
private Animation animation;
private Animation blendFrom;
private float time;
private float speed;
private float timeBlendFrom;
private float blendTime;
private float speedBlendFrom;
private boolean notified=false;
private LoopMode loopMode, loopModeBlendFrom;
private float blendAmount = 1f;
private float blendRate = 0;
public AnimChannel(){
}
public AnimChannel(AnimControl control){
this.control = control;
}
/**
* Returns the parent control of this AnimChannel.
*
* @return the parent control of this AnimChannel.
* @see AnimControl
*/
public AnimControl getControl() {
return control;
}
/**
* @return The name of the currently playing animation, or null if
* none is assigned.
*
* @see AnimChannel#setAnim(java.lang.String)
*/
public String getAnimationName() {
return animation != null ? animation.getName() : null;
}
/**
* @return The loop mode currently set for the animation. The loop mode
* determines what will happen to the animation once it finishes
* playing.
*
* For more information, see the LoopMode enum class.
* @see LoopMode
* @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode)
*/
public LoopMode getLoopMode() {
return loopMode;
}
/**
* @param loopMode Set the loop mode for the channel. The loop mode
* determines what will happen to the animation once it finishes
* playing.
*
* For more information, see the LoopMode enum class.
* @see LoopMode
*/
public void setLoopMode(LoopMode loopMode) {
this.loopMode = loopMode;
}
/**
* @return The speed that is assigned to the animation channel. The speed
* is a scale value starting from 0.0, at 1.0 the animation will play
* at its default speed.
*
* @see AnimChannel#setSpeed(float)
*/
public float getSpeed() {
return speed;
}
/**
* @param speed Set the speed of the animation channel. The speed
* is a scale value starting from 0.0, at 1.0 the animation will play
* at its default speed.
*/
public void setSpeed(float speed) {
this.speed = speed;
if(blendTime>0){
this.speedBlendFrom = speed;
blendTime = Math.min(blendTime, animation.getLength() / speed);
blendRate = 1/ blendTime;
}
}
/**
* @return The time of the currently playing animation. The time
* starts at 0 and continues on until getAnimMaxTime().
*
* @see AnimChannel#setTime(float)
*/
public float getTime() {
return time;
}
/**
* @param time Set the time of the currently playing animation, the time
* is clamped from 0 to {@link #getAnimMaxTime()}.
*/
public void setTime(float time) {
this.time = FastMath.clamp(time, 0, getAnimMaxTime());
}
/**
* @return The length of the currently playing animation, or zero
* if no animation is playing.
*
* @see AnimChannel#getTime()
*/
public float getAnimMaxTime(){
return animation != null ? animation.getLength() : 0f;
}
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* This resets the time to zero, and optionally blends the animation
* over <code>blendTime</code> seconds with the currently playing animation.
* Notice that this method will reset the control's speed to 1.0.
*
* @param name The name of the animation to play
* @param blendTime The blend time over which to blend the new animation
* with the old one. If zero, then no blending will occur and the new
* animation will be applied instantly.
*/
public void setAnim(String name, float blendTime){
if (name == null)
throw new IllegalArgumentException("name cannot be null");
if (blendTime < 0f)
throw new IllegalArgumentException("blendTime cannot be less than zero");
Animation anim = control.animationMap.get(name);
if (anim == null)
throw new IllegalArgumentException("Cannot find animation named: '"+name+"'");
control.notifyAnimChange(this, name);
if (animation != null && blendTime > 0f){
this.blendTime = blendTime;
// activate blending
blendTime = Math.min(blendTime, anim.getLength() / speed);
blendFrom = animation;
timeBlendFrom = time;
speedBlendFrom = speed;
loopModeBlendFrom = loopMode;
blendAmount = 0f;
blendRate = 1f / blendTime;
}else{
blendFrom = null;
}
animation = anim;
time = 0;
speed = 1f;
loopMode = LoopMode.Loop;
notified = false;
}
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* See {@link #setAnim(java.lang.String, float)}.
* The blendTime argument by default is 150 milliseconds.
*
* @param name The name of the animation to play
*/
public void setAnim(String name){
setAnim(name, DEFAULT_BLEND_TIME);
}
/**
* Add all the bones of the model's skeleton to be
* influenced by this animation channel.
*/
public void addAllBones() {
affectedBones = null;
}
/**
* Add a single bone to be influenced by this animation channel.
*/
public void addBone(String name) {
addBone(control.getSkeleton().getBone(name));
}
/**
* Add a single bone to be influenced by this animation channel.
*/
public void addBone(Bone bone) {
int boneIndex = control.getSkeleton().getBoneIndex(bone);
if(affectedBones == null) {
affectedBones = new BitSet(control.getSkeleton().getBoneCount());
}
affectedBones.set(boneIndex);
}
/**
* Add bones to be influenced by this animation channel starting from the
* given bone name and going toward the root bone.
*/
public void addToRootBone(String name) {
addToRootBone(control.getSkeleton().getBone(name));
}
/**
* Add bones to be influenced by this animation channel starting from the
* given bone and going toward the root bone.
*/
public void addToRootBone(Bone bone) {
addBone(bone);
while (bone.getParent() != null) {
bone = bone.getParent();
addBone(bone);
}
}
/**
* Add bones to be influenced by this animation channel, starting
* from the given named bone and going toward its children.
*/
public void addFromRootBone(String name) {
addFromRootBone(control.getSkeleton().getBone(name));
}
/**
* Add bones to be influenced by this animation channel, starting
* from the given bone and going toward its children.
*/
public void addFromRootBone(Bone bone) {
addBone(bone);
if (bone.getChildren() == null)
return;
for (Bone childBone : bone.getChildren()) {
addBone(childBone);
addFromRootBone(childBone);
}
}
BitSet getAffectedBones(){
return affectedBones;
}
public void reset(boolean rewind){
if(rewind){
setTime(0);
if(control.getSkeleton()!=null){
control.getSkeleton().resetAndUpdate();
}else{
TempVars vars = TempVars.get();
update(0, vars);
vars.release();
}
}
animation = null;
notified = false;
}
void update(float tpf, TempVars vars) {
if (animation == null)
return;
if (blendFrom != null && blendAmount != 1.0f){
// The blendFrom anim is set, the actual animation
// playing will be set
// blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
timeBlendFrom += tpf * speedBlendFrom;
timeBlendFrom = AnimationUtils.clampWrapTime(timeBlendFrom,
blendFrom.getLength(),
loopModeBlendFrom);
if (timeBlendFrom < 0){
timeBlendFrom = -timeBlendFrom;
speedBlendFrom = -speedBlendFrom;
}
blendAmount += tpf * blendRate;
if (blendAmount > 1f){
blendAmount = 1f;
blendFrom = null;
}
}
animation.setTime(time, blendAmount, control, this, vars);
time += tpf * speed;
if (animation.getLength() > 0){
if (!notified && (time >= animation.getLength() || time < 0)) {
if (loopMode == LoopMode.DontLoop) {
// Note that this flag has to be set before calling the notify
// since the notify may start a new animation and then unset
// the flag.
notified = true;
}
control.notifyAnimCycleDone(this, animation.getName());
}
}
time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode);
if (time < 0){
// Negative time indicates that speed should be inverted
// (for cycle loop mode only)
time = -time;
speed = -speed;
}
}
}

@ -93,6 +93,24 @@ public class Animation implements Savable, Cloneable, JmeCloneable {
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 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
SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class);
for( Track track : tracks ) {
if( track instanceof ClonableTrack ) {
if (track instanceof JmeCloneable) {
newTracks.add(cloner.clone(track));
} else {
// this is the part that seems fishy

@ -31,17 +31,15 @@
*/
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
*/
public class AnimationUtils {
public AnimationUtils(){
}
/**
* Clamps the time according to duration and loopMode
* @param time
@ -50,7 +48,7 @@ public class AnimationUtils {
* @return
*/
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
}
switch (loopMode) {

@ -32,15 +32,15 @@
package com.jme3.animation;
import com.jme3.export.*;
import com.jme3.material.MatParamOverride;
import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.*;
import com.jme3.shader.VarType;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.ArrayList;
@ -723,6 +723,8 @@ public final class Bone implements Savable, JmeCloneable {
if (attachNode == null) {
attachNode = new Node(name + "_attachnode");
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;

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

@ -32,28 +32,24 @@
package com.jme3.animation;
import com.jme3.export.*;
import com.jme3.material.MatParam;
import com.jme3.material.Material;
import com.jme3.material.MatParamOverride;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.*;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.shader.VarType;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -108,10 +104,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* Bone offset matrices, recreated each frame
*/
private transient Matrix4f[] offsetMatrices;
/**
* Material references used for hardware skinning
*/
private Set<Material> materials = new HashSet<Material>();
private MatParamOverride numberOfBonesParam;
private MatParamOverride boneMatricesParam;
/**
* Serialization only. Do not use.
@ -120,11 +116,13 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
}
private void switchToHardware() {
numberOfBonesParam.setEnabled(true);
boneMatricesParam.setEnabled(true);
// Next full 10 bones (e.g. 30 on 24 bones)
int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
for (Material m : materials) {
m.setInt("NumberOfBones", numBones);
}
numberOfBonesParam.setValue(numBones);
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
@ -134,11 +132,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
}
private void switchToSoftware() {
for (Material m : materials) {
if (m.getParam("NumberOfBones") != null) {
m.clearParam("NumberOfBones");
}
}
numberOfBonesParam.setEnabled(false);
boneMatricesParam.setEnabled(false);
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
@ -148,17 +144,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
}
private boolean testHardwareSupported(RenderManager rm) {
for (Material m : materials) {
// Some of the animated mesh(es) do not support hardware skinning,
// so it is not supported by the model.
if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) {
Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING,
"Not using hardware skinning for {0}, " +
"because material {1} doesn''t support it.",
new Object[]{spatial, m.getMaterialDef().getName()});
return false;
}
//Only 255 bones max supported with hardware skinning
if (skeleton.getBoneCount() > 255) {
return false;
}
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, then it shall be disabled.
*
* @param preferred
* @see #isHardwareSkinningUsed()
*/
public void setHardwareSkinningPreferred(boolean preferred) {
@ -211,6 +201,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
throw new IllegalArgumentException("skeleton cannot be null");
}
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();
if (mesh != null && mesh.isAnimated()) {
targets.add(geometry);
materials.add(geometry.getMaterial());
}
}
private void findTargets(Node node) {
@ -237,8 +229,21 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
@Override
public void setSpatial(Spatial spatial) {
Spatial oldSpatial = this.spatial;
super.setSpatial(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() {
@ -257,27 +262,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
private void controlRenderHardware() {
offsetMatrices = skeleton.computeSkinningMatrices();
for (Material m : materials) {
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);
}
boneMatricesParam.setValue(offsetMatrices);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
@ -295,7 +281,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
if (hwSkinningSupported) {
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 {
switchToSoftware();
}
@ -419,28 +405,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
// were shared then this will share them.
this.targets = cloner.clone(targets);
// Not automatic set cloning yet
Set<Material> newMaterials = new HashSet<Material>();
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;
this.numberOfBonesParam = cloner.clone(numberOfBonesParam);
this.boneMatricesParam = cloner.clone(boneMatricesParam);
}
/**
@ -533,7 +499,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
if (maxWeightsPerVert <= 0) {
throw new IllegalStateException("Max weights per vert is incorrectly set!");
}
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
// NOTE: This code assumes the vertex buffer is in bind pose
@ -547,14 +512,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
fnb.rewind();
// 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();
ib.rewind();
wb.rewind();
float[] weights = wb.array();
byte[] indices = ib.array();
int idxWeights = 0;
TempVars vars = TempVars.get();
@ -592,7 +555,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
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;
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
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();
ib.rewind();
wb.rewind();
float[] weights = wb.array();
byte[] indices = ib.array();
int idxWeights = 0;
TempVars vars = TempVars.get();
@ -725,7 +686,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
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;
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);
OutputCapsule oc = ex.getCapsule(this);
oc.write(skeleton, "skeleton", null);
//Targets and materials don't need to be saved, they'll be gathered on each frame
oc.write(numberOfBonesParam, "numberOfBonesParam", null);
oc.write(boneMatricesParam, "boneMatricesParam", null);
}
@Override
@ -791,6 +754,16 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
super.read(im);
InputCapsule in = im.getCapsule(this);
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) {
targets.clear();
materials.clear();
if (spatial instanceof Node) {
findTargets((Node) spatial);

@ -39,6 +39,9 @@ import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.Arrays;
@ -47,7 +50,7 @@ import java.util.Arrays;
*
* @author Marcin Roguski (Kaelthas)
*/
public class SpatialTrack implements Track {
public class SpatialTrack implements Track, JmeCloneable {
/**
* Translations of the track.
@ -63,7 +66,13 @@ public class SpatialTrack implements Track {
* Scales of the track.
*/
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.
*/
@ -97,8 +106,11 @@ public class SpatialTrack implements Track {
* the current time of the animation
*/
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 tempS = vars.vect2;
Quaternion tempQ = vars.quat1;
@ -152,11 +164,13 @@ public class SpatialTrack implements Track {
tempV.interpolateLocal(tempV2, blend);
tempS.interpolateLocal(tempS2, blend);
}
if (translations != null)
if (translations != null) {
spatial.setLocalTranslation(tempV);
if (rotations != null)
}
if (rotations != null) {
spatial.setLocalRotation(tempQ);
}
if (scales != null) {
spatial.setLocalScale(tempS);
}
@ -235,18 +249,27 @@ public class SpatialTrack implements Track {
public float getLength() {
return times == null ? 0 : times[times.length - 1] - times[0];
}
@Override
public Track clone() {
return (Track) jmeClone();
}
@Override
public float[] getKeyFrameTimes() {
return times;
}
/**
* This method creates a clone of the current object.
* @return a clone of the current object
*/
public void setTrackSpatial(Spatial trackSpatial) {
this.trackSpatial = trackSpatial;
}
public Spatial getTrackSpatial() {
return trackSpatial;
}
@Override
public SpatialTrack clone() {
public Object jmeClone() {
int tablesLength = times.length;
float[] timesCopy = this.times.clone();
@ -257,6 +280,11 @@ public class SpatialTrack implements Track {
//need to use the constructor here because of the final fields used in this class
return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.trackSpatial = cloner.clone(((SpatialTrack) original).trackSpatial);
}
@Override
public void write(JmeExporter ex) throws IOException {
@ -265,6 +293,7 @@ public class SpatialTrack implements Track {
oc.write(rotations, "rotations", null);
oc.write(times, "times", null);
oc.write(scales, "scales", null);
oc.write(trackSpatial, "trackSpatial", null);
}
@Override
@ -274,5 +303,6 @@ public class SpatialTrack implements Track {
rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
times = ic.readFloatArray("times", null);
scales = (CompactVector3Array) ic.readSavable("scales", null);
trackSpatial = (Spatial) ic.readSavable("trackSpatial", null);
}
}

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

File diff suppressed because it is too large Load Diff

@ -1,89 +1,93 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic;
import com.jme3.cinematic.events.CinematicEvent;
import com.jme3.export.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Nehon
*/
public class KeyFrame implements Savable {
List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>();
private int index;
public List<CinematicEvent> getCinematicEvents() {
return cinematicEvents;
}
public void setCinematicEvents(List<CinematicEvent> cinematicEvents) {
this.cinematicEvents = cinematicEvents;
}
public List<CinematicEvent> trigger() {
for (CinematicEvent event : cinematicEvents) {
event.play();
}
return cinematicEvents;
}
public boolean isEmpty(){
return cinematicEvents.isEmpty();
}
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
oc.write(index, "index", 0);
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
index=ic.readInt("index", 0);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic;
import com.jme3.cinematic.events.CinematicEvent;
import com.jme3.export.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Nehon
*/
public class KeyFrame implements Savable {
public KeyFrame(){
}
List<CinematicEvent> cinematicEvents = new ArrayList<>();
private int index;
public List<CinematicEvent> getCinematicEvents() {
return cinematicEvents;
}
public void setCinematicEvents(List<CinematicEvent> cinematicEvents) {
this.cinematicEvents = cinematicEvents;
}
public List<CinematicEvent> trigger() {
for (CinematicEvent event : cinematicEvents) {
event.play();
}
return cinematicEvents;
}
public boolean isEmpty(){
return cinematicEvents.isEmpty();
}
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
oc.write(index, "index", 0);
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
index=ic.readInt("index", 0);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}

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

@ -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);
}
}

@ -1,491 +1,491 @@
/*
* Copyright (c) 2009-2016 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic.events;
import com.jme3.animation.AnimationUtils;
import com.jme3.animation.LoopMode;
import com.jme3.app.Application;
import com.jme3.cinematic.Cinematic;
import com.jme3.cinematic.MotionPath;
import com.jme3.cinematic.PlayState;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* A MotionEvent is a control over the spatial that manages the position and direction of the spatial while following a motion Path.
*
* You must first create a MotionPath and then create a MotionEvent to associate a spatial and the path.
*
* @author Nehon
*/
public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable {
protected Spatial spatial;
protected int currentWayPoint;
protected float currentValue;
protected Vector3f direction = new Vector3f();
protected Vector3f lookAt = null;
protected Vector3f upVector = Vector3f.UNIT_Y;
protected Quaternion rotation = null;
protected Direction directionType = Direction.None;
protected MotionPath path;
private boolean isControl = true;
private int travelDirection = 1;
/**
* the distance traveled by the spatial on the path
*/
protected float traveledDistance = 0;
/**
* Enum for the different type of target direction behavior.
*/
public enum Direction {
/**
* The target stays in the starting direction.
*/
None,
/**
* The target rotates with the direction of the path.
*/
Path,
/**
* The target rotates with the direction of the path but with the addition of a rotation.
* You need to use the setRotation method when using this Direction.
*/
PathAndRotation,
/**
* The target rotates with the given rotation.
*/
Rotation,
/**
* The target looks at a point.
* You need to use the setLookAt method when using this direction.
*/
LookAt
}
/**
* Create MotionEvent,
* when using this constructor don't forget to assign spatial and path.
*/
public MotionEvent() {
super();
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path) {
super();
spatial.addControl(this);
this.path = path;
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) {
super(initialDuration);
spatial.addControl(this);
this.path = path;
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) {
super();
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
super(initialDuration);
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
}
public void update(float tpf) {
if (isControl) {
internalUpdate(tpf);
}
}
@Override
public void internalUpdate(float tpf) {
if (playState == PlayState.Playing) {
time = time + (tpf * speed);
if (loopMode == LoopMode.Loop && time < 0) {
time = initialDuration;
}
if ((time >= initialDuration || time < 0) && loopMode == LoopMode.DontLoop) {
if (time >= initialDuration) {
path.triggerWayPointReach(path.getNbWayPoints() - 1, this);
}
stop();
} else {
time = AnimationUtils.clampWrapTime(time, initialDuration, loopMode);
if(time<0){
speed = - speed;
time = - time;
}
onUpdate(tpf);
}
}
}
@Override
public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic);
isControl = false;
}
@Override
public void setTime(float time) {
super.setTime(time);
onUpdate(0);
}
public void onUpdate(float tpf) {
traveledDistance = path.interpolatePath(time, this, tpf);
computeTargetDirection();
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(lookAt, "lookAt", null);
oc.write(upVector, "upVector", Vector3f.UNIT_Y);
oc.write(rotation, "rotation", null);
oc.write(directionType, "directionType", Direction.None);
oc.write(path, "path", null);
oc.write(spatial, "spatial", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
lookAt = (Vector3f) in.readSavable("lookAt", null);
upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y);
rotation = (Quaternion) in.readSavable("rotation", null);
directionType = in.readEnum("directionType", Direction.class, Direction.None);
path = (MotionPath) in.readSavable("path", null);
spatial = (Spatial) in.readSavable("spatial", null);
}
/**
* This method is meant to be called by the motion path only.
* @return
*/
public boolean needsDirection() {
return directionType == Direction.Path || directionType == Direction.PathAndRotation;
}
private void computeTargetDirection() {
switch (directionType) {
case Path:
Quaternion q = new Quaternion();
q.lookAt(direction, upVector);
spatial.setLocalRotation(q);
break;
case LookAt:
if (lookAt != null) {
spatial.lookAt(lookAt, upVector);
}
break;
case PathAndRotation:
if (rotation != null) {
Quaternion q2 = new Quaternion();
q2.lookAt(direction, upVector);
q2.multLocal(rotation);
spatial.setLocalRotation(q2);
}
break;
case Rotation:
if (rotation != null) {
spatial.setLocalRotation(rotation);
}
break;
case None:
break;
default:
break;
}
}
/**
* Clone this control for the given spatial.
* @param spatial
* @return
*/
@Override
public Control cloneForSpatial(Spatial spatial) {
MotionEvent control = new MotionEvent();
control.setPath(path);
control.playState = playState;
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
control.directionType = directionType;
return control;
}
@Override
public Object jmeClone() {
MotionEvent control = new MotionEvent();
control.path = path;
control.playState = playState;
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
control.directionType = directionType;
control.spatial = spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
@Override
public void onPlay() {
traveledDistance = 0;
}
@Override
public void onStop() {
currentWayPoint = 0;
}
@Override
public void onPause() {
}
/**
* This method is meant to be called by the motion path only.
* @return
*/
public float getCurrentValue() {
return currentValue;
}
/**
* This method is meant to be called by the motion path only.
*
*/
public void setCurrentValue(float currentValue) {
this.currentValue = currentValue;
}
/**
* This method is meant to be called by the motion path only.
* @return
*/
public int getCurrentWayPoint() {
return currentWayPoint;
}
/**
* This method is meant to be called by the motion path only.
*
*/
public void setCurrentWayPoint(int currentWayPoint) {
this.currentWayPoint = currentWayPoint;
}
/**
* Returns the direction the spatial is moving.
* @return
*/
public Vector3f getDirection() {
return direction;
}
/**
* Sets the direction of the spatial, using the Y axis as the up vector.
* Use MotionEvent#setDirection((Vector3f direction,Vector3f upVector) if
* you want a custum up vector.
* This method is used by the motion path.
* @param direction
*/
public void setDirection(Vector3f direction) {
setDirection(direction, Vector3f.UNIT_Y);
}
/**
* Sets the direction of the spatial with the given up vector.
* This method is used by the motion path.
* @param direction
* @param upVector the up vector to consider for this direction.
*/
public void setDirection(Vector3f direction,Vector3f upVector) {
this.direction.set(direction);
this.upVector.set(upVector);
}
/**
* Returns the direction type of the target.
* @return the direction type.
*/
public Direction getDirectionType() {
return directionType;
}
/**
* Sets the direction type of the target.
* On each update the direction given to the target can have different behavior.
* See the Direction Enum for explanations.
* @param directionType the direction type.
*/
public void setDirectionType(Direction directionType) {
this.directionType = directionType;
}
/**
* Set the lookAt for the target.
* This can be used only if direction Type is Direction.LookAt.
* @param lookAt the position to look at.
* @param upVector the up vector.
*/
public void setLookAt(Vector3f lookAt, Vector3f upVector) {
this.lookAt = lookAt;
this.upVector = upVector;
}
/**
* Returns the rotation of the target.
* @return the rotation quaternion.
*/
public Quaternion getRotation() {
return rotation;
}
/**
* Sets the rotation of the target.
* This can be used only if direction Type is Direction.PathAndRotation or Direction.Rotation.
* With PathAndRotation the target will face the direction of the path multiplied by the given Quaternion.
* With Rotation the rotation of the target will be set with the given Quaternion.
* @param rotation the rotation quaternion.
*/
public void setRotation(Quaternion rotation) {
this.rotation = rotation;
}
/**
* Return the motion path this control follows.
* @return
*/
public MotionPath getPath() {
return path;
}
/**
* Sets the motion path to follow.
* @param path
*/
public void setPath(MotionPath path) {
this.path = path;
}
public void setEnabled(boolean enabled) {
if (enabled) {
play();
} else {
pause();
}
}
public boolean isEnabled() {
return playState != PlayState.Stopped;
}
public void render(RenderManager rm, ViewPort vp) {
}
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
}
public Spatial getSpatial() {
return spatial;
}
/**
* Return the distance traveled by the spatial on the path.
* @return
*/
public float getTraveledDistance() {
return traveledDistance;
}
}
/*
* Copyright (c) 2009-2016 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic.events;
import com.jme3.animation.AnimationUtils;
import com.jme3.animation.LoopMode;
import com.jme3.app.Application;
import com.jme3.cinematic.Cinematic;
import com.jme3.cinematic.MotionPath;
import com.jme3.cinematic.PlayState;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* A MotionEvent is a control over the spatial that manages the position and direction of the spatial while following a motion Path.
*
* You must first create a MotionPath and then create a MotionEvent to associate a spatial and the path.
*
* @author Nehon
*/
public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable {
protected Spatial spatial;
protected int currentWayPoint;
protected float currentValue;
protected Vector3f direction = new Vector3f();
protected Vector3f lookAt = null;
protected Vector3f upVector = Vector3f.UNIT_Y;
protected Quaternion rotation = null;
protected Direction directionType = Direction.None;
protected MotionPath path;
private boolean isControl = true;
private int travelDirection = 1;
/**
* the distance traveled by the spatial on the path
*/
protected float traveledDistance = 0;
/**
* Enum for the different type of target direction behavior.
*/
public enum Direction {
/**
* The target stays in the starting direction.
*/
None,
/**
* The target rotates with the direction of the path.
*/
Path,
/**
* The target rotates with the direction of the path but with the addition of a rotation.
* You need to use the setRotation method when using this Direction.
*/
PathAndRotation,
/**
* The target rotates with the given rotation.
*/
Rotation,
/**
* The target looks at a point.
* You need to use the setLookAt method when using this direction.
*/
LookAt
}
/**
* Create MotionEvent,
* when using this constructor don't forget to assign spatial and path.
*/
public MotionEvent() {
super();
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path) {
super();
spatial.addControl(this);
this.path = path;
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) {
super(initialDuration);
spatial.addControl(this);
this.path = path;
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) {
super();
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
}
/**
* Creates a MotionPath for the given spatial on the given motion path.
* @param spatial
* @param path
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
super(initialDuration);
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
}
public void update(float tpf) {
if (isControl) {
internalUpdate(tpf);
}
}
@Override
public void internalUpdate(float tpf) {
if (playState == PlayState.Playing) {
time = time + (tpf * speed);
if (loopMode == LoopMode.Loop && time < 0) {
time = initialDuration;
}
if ((time >= initialDuration || time < 0) && loopMode == LoopMode.DontLoop) {
if (time >= initialDuration) {
path.triggerWayPointReach(path.getNbWayPoints() - 1, this);
}
stop();
} else {
time = AnimationUtils.clampWrapTime(time, initialDuration, loopMode);
if(time<0){
speed = - speed;
time = - time;
}
onUpdate(tpf);
}
}
}
@Override
public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic);
isControl = false;
}
@Override
public void setTime(float time) {
super.setTime(time);
onUpdate(0);
}
public void onUpdate(float tpf) {
traveledDistance = path.interpolatePath(time, this, tpf);
computeTargetDirection();
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(lookAt, "lookAt", null);
oc.write(upVector, "upVector", Vector3f.UNIT_Y);
oc.write(rotation, "rotation", null);
oc.write(directionType, "directionType", Direction.None);
oc.write(path, "path", null);
oc.write(spatial, "spatial", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
lookAt = (Vector3f) in.readSavable("lookAt", null);
upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y);
rotation = (Quaternion) in.readSavable("rotation", null);
directionType = in.readEnum("directionType", Direction.class, Direction.None);
path = (MotionPath) in.readSavable("path", null);
spatial = (Spatial) in.readSavable("spatial", null);
}
/**
* This method is meant to be called by the motion path only.
* @return
*/
public boolean needsDirection() {
return directionType == Direction.Path || directionType == Direction.PathAndRotation;
}
private void computeTargetDirection() {
switch (directionType) {
case Path:
Quaternion q = new Quaternion();
q.lookAt(direction, upVector);
spatial.setLocalRotation(q);
break;
case LookAt:
if (lookAt != null) {
spatial.lookAt(lookAt, upVector);
}
break;
case PathAndRotation:
if (rotation != null) {
Quaternion q2 = new Quaternion();
q2.lookAt(direction, upVector);
q2.multLocal(rotation);
spatial.setLocalRotation(q2);
}
break;
case Rotation:
if (rotation != null) {
spatial.setLocalRotation(rotation);
}
break;
case None:
break;
default:
break;
}
}
/**
* Clone this control for the given spatial.
* @param spatial
* @return
*/
@Override
public Control cloneForSpatial(Spatial spatial) {
MotionEvent control = new MotionEvent();
control.setPath(path);
control.playState = playState;
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
control.directionType = directionType;
return control;
}
@Override
public Object jmeClone() {
MotionEvent control = new MotionEvent();
control.path = path;
control.playState = playState;
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
control.directionType = directionType;
control.spatial = spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
@Override
public void onPlay() {
traveledDistance = 0;
}
@Override
public void onStop() {
currentWayPoint = 0;
}
@Override
public void onPause() {
}
/**
* This method is meant to be called by the motion path only.
* @return
*/
public float getCurrentValue() {
return currentValue;
}
/**
* This method is meant to be called by the motion path only.
*
*/
public void setCurrentValue(float currentValue) {
this.currentValue = currentValue;
}
/**
* This method is meant to be called by the motion path only.
* @return
*/
public int getCurrentWayPoint() {
return currentWayPoint;
}
/**
* This method is meant to be called by the motion path only.
*
*/
public void setCurrentWayPoint(int currentWayPoint) {
this.currentWayPoint = currentWayPoint;
}
/**
* Returns the direction the spatial is moving.
* @return
*/
public Vector3f getDirection() {
return direction;
}
/**
* Sets the direction of the spatial, using the Y axis as the up vector.
* Use MotionEvent#setDirection((Vector3f direction,Vector3f upVector) if
* you want a custum up vector.
* This method is used by the motion path.
* @param direction
*/
public void setDirection(Vector3f direction) {
setDirection(direction, Vector3f.UNIT_Y);
}
/**
* Sets the direction of the spatial with the given up vector.
* This method is used by the motion path.
* @param direction
* @param upVector the up vector to consider for this direction.
*/
public void setDirection(Vector3f direction,Vector3f upVector) {
this.direction.set(direction);
this.upVector.set(upVector);
}
/**
* Returns the direction type of the target.
* @return the direction type.
*/
public Direction getDirectionType() {
return directionType;
}
/**
* Sets the direction type of the target.
* On each update the direction given to the target can have different behavior.
* See the Direction Enum for explanations.
* @param directionType the direction type.
*/
public void setDirectionType(Direction directionType) {
this.directionType = directionType;
}
/**
* Set the lookAt for the target.
* This can be used only if direction Type is Direction.LookAt.
* @param lookAt the position to look at.
* @param upVector the up vector.
*/
public void setLookAt(Vector3f lookAt, Vector3f upVector) {
this.lookAt = lookAt;
this.upVector = upVector;
}
/**
* Returns the rotation of the target.
* @return the rotation quaternion.
*/
public Quaternion getRotation() {
return rotation;
}
/**
* Sets the rotation of the target.
* This can be used only if direction Type is Direction.PathAndRotation or Direction.Rotation.
* With PathAndRotation the target will face the direction of the path multiplied by the given Quaternion.
* With Rotation the rotation of the target will be set with the given Quaternion.
* @param rotation the rotation quaternion.
*/
public void setRotation(Quaternion rotation) {
this.rotation = rotation;
}
/**
* Return the motion path this control follows.
* @return
*/
public MotionPath getPath() {
return path;
}
/**
* Sets the motion path to follow.
* @param path
*/
public void setPath(MotionPath path) {
this.path = path;
}
public void setEnabled(boolean enabled) {
if (enabled) {
play();
} else {
pause();
}
}
public boolean isEnabled() {
return playState != PlayState.Stopped;
}
public void render(RenderManager rm, ViewPort vp) {
}
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
}
public Spatial getSpatial() {
return spatial;
}
/**
* Return the distance traveled by the spatial on the path.
* @return
*/
public float getTraveledDistance() {
return traveledDistance;
}
}

@ -1,229 +1,230 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic.events;
import com.jme3.animation.LoopMode;
import com.jme3.app.Application;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import com.jme3.cinematic.Cinematic;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import java.io.IOException;
/**
* A sound track to be played in a cinematic.
* @author Nehon
*/
public class SoundEvent extends AbstractCinematicEvent {
protected String path;
protected AudioNode audioNode;
protected boolean stream = false;
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
*/
public SoundEvent(String path) {
this.path = path;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
*/
public SoundEvent(String path, boolean stream) {
this(path);
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
* @param initialDuration the initial duration of the event
*/
public SoundEvent(String path, boolean stream, float initialDuration) {
super(initialDuration);
this.path = path;
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, boolean stream, LoopMode loopMode) {
super(loopMode);
this.path = path;
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
* @param initialDuration the initial duration of the event
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, boolean stream, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode);
this.path = path;
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param initialDuration the initial duration of the event
*/
public SoundEvent(String path, float initialDuration) {
super(initialDuration);
this.path = path;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, LoopMode loopMode) {
super(loopMode);
this.path = path;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param initialDuration the initial duration of the event
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode);
this.path = path;
}
/**
* creates a sound event
* used for serialization
*/
public SoundEvent() {
}
@Override
public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic);
audioNode = new AudioNode(app.getAssetManager(), path, stream);
audioNode.setPositional(false);
setLoopMode(loopMode);
}
@Override
public void setTime(float time) {
super.setTime(time);
//can occur on rewind
if (time < 0f) {
stop();
}else{
audioNode.setTimeOffset(time);
}
}
@Override
public void onPlay() {
audioNode.play();
}
@Override
public void onStop() {
audioNode.stop();
}
@Override
public void onPause() {
audioNode.pause();
}
@Override
public void onUpdate(float tpf) {
if (audioNode.getStatus() == AudioSource.Status.Stopped) {
stop();
}
}
/**
* Returns the underlying audio node of this sound track
* @return
*/
public AudioNode getAudioNode() {
return audioNode;
}
@Override
public void setLoopMode(LoopMode loopMode) {
super.setLoopMode(loopMode);
if (loopMode != LoopMode.DontLoop) {
audioNode.setLooping(true);
} else {
audioNode.setLooping(false);
}
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(path, "path", "");
oc.write(stream, "stream", false);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
path = ic.readString("path", "");
stream = ic.readBoolean("stream", false);
}
}
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cinematic.events;
import com.jme3.animation.LoopMode;
import com.jme3.app.Application;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import com.jme3.cinematic.Cinematic;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import java.io.IOException;
/**
* A sound track to be played in a cinematic.
* @author Nehon
*/
public class SoundEvent extends AbstractCinematicEvent {
protected String path;
protected AudioNode audioNode;
protected boolean stream = false;
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
*/
public SoundEvent(String path) {
this.path = path;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
*/
public SoundEvent(String path, boolean stream) {
this(path);
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
* @param initialDuration the initial duration of the event
*/
public SoundEvent(String path, boolean stream, float initialDuration) {
super(initialDuration);
this.path = path;
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, boolean stream, LoopMode loopMode) {
super(loopMode);
this.path = path;
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param stream true to make the audio data streamed
* @param initialDuration the initial duration of the event
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, boolean stream, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode);
this.path = path;
this.stream = stream;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param initialDuration the initial duration of the event
*/
public SoundEvent(String path, float initialDuration) {
super(initialDuration);
this.path = path;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, LoopMode loopMode) {
super(loopMode);
this.path = path;
}
/**
* creates a sound track from the given resource path
* @param path the path to an audio file (ie : "Sounds/mySound.wav")
* @param initialDuration the initial duration of the event
* @param loopMode the loopMode
* @see LoopMode
*/
public SoundEvent(String path, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode);
this.path = path;
}
/**
* creates a sound event
* used for serialization
*/
public SoundEvent() {
super();
}
@Override
public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic);
audioNode = new AudioNode(app.getAssetManager(), path, stream);
audioNode.setPositional(false);
setLoopMode(loopMode);
}
@Override
public void setTime(float time) {
super.setTime(time);
//can occur on rewind
if (time < 0f) {
stop();
}else{
audioNode.setTimeOffset(time);
}
}
@Override
public void onPlay() {
audioNode.play();
}
@Override
public void onStop() {
audioNode.stop();
}
@Override
public void onPause() {
audioNode.pause();
}
@Override
public void onUpdate(float tpf) {
if (audioNode.getStatus() == AudioSource.Status.Stopped) {
stop();
}
}
/**
* Returns the underlying audio node of this sound track
* @return
*/
public AudioNode getAudioNode() {
return audioNode;
}
@Override
public void setLoopMode(LoopMode loopMode) {
super.setLoopMode(loopMode);
if (loopMode != LoopMode.DontLoop) {
audioNode.setLooping(true);
} else {
audioNode.setLooping(false);
}
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(path, "path", "");
oc.write(stream, "stream", false);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
path = ic.readString("path", "");
stream = ic.readBoolean("stream", false);
}
}

@ -1016,7 +1016,7 @@ public class ParticleEmitter extends Geometry {
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
p.velocity.x -= gravity.x * 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.image.ColorSpace;
import com.jme3.util.BufferUtils;
import com.jme3.util.MipMapGenerator;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -72,6 +73,8 @@ public class EnvironmentCamera extends BaseAppState {
protected Image.Format imageFormat = Image.Format.RGB16F;
public TextureCubeMap debugEnv;
//Axis for cameras
static {
//PositiveX axis(left, up, direction)
@ -108,7 +111,10 @@ public class EnvironmentCamera extends BaseAppState {
protected Vector3f position = new Vector3f();
protected ColorRGBA backGroundColor;
protected int size = 128;
/**
* The size of environment cameras.
*/
protected int size = 256;
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);
renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat);
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);
debugEnv = map;
job.callback.done(map);
map.getImage().dispose();
jobs.remove(0);
}
/**
* Gets the size of environment cameras.
*
* @return the size of environment cameras.
*/
public int getSize() {
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() {
return position;
}
@ -224,8 +259,7 @@ public class EnvironmentCamera extends BaseAppState {
this.backGroundColor = app.getViewPort().getBackgroundColor();
final Camera[] cameras = new Camera[6];
Texture2D[] textures = new Texture2D[6];
final Texture2D[] textures = new Texture2D[6];
viewports = new ViewPort[6];
framebuffers = new FrameBuffer[6];
@ -241,6 +275,7 @@ public class EnvironmentCamera extends BaseAppState {
}
}
@Override
protected void cleanup(Application app) {
this.backGroundColor = null;

@ -31,16 +31,14 @@
*/
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.environment.generation.*;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
@ -109,94 +107,99 @@ public class LightProbeFactory {
* @param envCam the EnvironmentCamera
* @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.
* @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();
probe.setPosition(envCam.getPosition());
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
generatePbrMaps(map, probe, envCam.getApplication(), genType, listener);
}
});
return probe;
}
/**
* Updates a LightProbe with the giver EnvironmentCamera in the given scene.
*
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 given EnvironmentCamera in the given scene.
* <p>
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* <p>
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @param probe the Light probe to update
* @param envCam the EnvironmentCamera
* @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.
* @return the created LightProbe
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
*
* @param probe the Light probe to update
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
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());
probe.setReady(false);
if(probe.getIrradianceMap() != null) {
probe.getIrradianceMap().getImage().dispose();
if (probe.getPrefilteredEnvMap() != null) {
probe.getPrefilteredEnvMap().getImage().dispose();
}
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
generatePbrMaps(map, probe, envCam.getApplication(), genType, listener);
}
});
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.
* 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.
*
*
* @param envMap the raw env map rendered by the env camera
* @param probe the LigthProbe to generate maps for
* @param app the Application
* @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) {
IrradianceMapGenerator irrMapGenerator;
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
IrradianceSphericalHarmonicsGenerator irrShGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
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();
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++) {
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]);
}
}
@ -280,6 +283,7 @@ public class LightProbeFactory {
jobState.done[index] = true;
if (jobState.isDone()) {
probe.setNbMipMaps(probe.getPrefilteredEnvMap().getImage().getMipMapSizes().length);
probe.setReady(true);
if (globalListener != null) {
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;
import com.jme3.app.Application;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
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.math.*;
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.logging.Level;
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
* be lauched from a separate thread.
*
* <p>
* TODO there is a lot of duplicate code here with the EnvMapUtils.
*
* @author Nehon
@ -68,6 +61,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private EnvMapUtils.GenerationType genType;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
@ -85,8 +79,8 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* the call to the EnvironmentCamera when the generation is done, so that
* this process is thread safe.
*
* @param app the Application
* @param face the face to generate
* @param app the Application
* @param face the face to generate
* @param listener
*/
public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressListener<Integer> listener) {
@ -95,36 +89,35 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
this.face = face;
}
/**
* 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 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.
* {@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) {
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, EnvMapUtils.GenerationType genType, TextureCubeMap store) {
this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod;
this.store = store;
this.genType = genType;
init();
}
private void init(){
Xi.set(0, 0, 0, 0);
H.set(0, 0, 0);
tmp.set(0, 0, 0);
c.set(1, 1, 1, 1);
tmp1.set(0, 0, 0);
tmp2.set(0, 0, 0);
tmp3.set(0, 0, 0);
reset();
private void init() {
Xi.set(0, 0, 0, 0);
H.set(0, 0, 0);
tmp.set(0, 0, 0);
c.set(1, 1, 1, 1);
tmp1.set(0, 0, 0);
tmp2.set(0, 0, 0);
tmp3.set(0, 0, 0);
reset();
}
@ -157,81 +150,191 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* Note that the output cube map is in RGBA8 format.
*
* @param sourceEnvMap
* @param targetMapSize the size of the irradiance map to generate
* @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
*/
private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
setEnd(nbMipMap);
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
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, EnvMapUtils.FixSeamsMethod.Wrap);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
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);
try {
TextureCubeMap pem = store;
int nbMipMap = store.getImage().getMipMapSizes().length;
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 targetWrapper = new CubeMapWrapper(pem);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
int targetMipMapSize = targetMapSize;
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
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);
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);
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;
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²
float a2 = roughness * roughness;
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++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H);
H = importanceSampleGGX(Xi, a2, H);
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;
float VoH = H.z;
Vector3f L = H.multLocal(VoH * 2f).subtractLocal(V);
float NoL = L.z;
float computedMipLevel = mipLevel;
if (mipLevel != 0) {
computedMipLevel = computeMipLevel(roughness, numSamples, this.targetMapSize, VoH);
}
toWorld(L, N, tangentX, tangentY, lWorld);
totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor);
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.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);
}
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
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);
}
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) {
store = new Vector3f();
}
@ -242,22 +345,9 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
Vector3f upVector = Vector3f.UNIT_X;
if (abs(normal.z) < 0.999) {
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);
store.x = sinThetaCosPhi;
store.y = sinThetaSinPhi;
store.z = cosTheta;
return store;
}

@ -31,17 +31,15 @@
*/
package com.jme3.environment.util;
import com.jme3.environment.util.EnvMapUtils;
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.math.*;
import com.jme3.texture.Image;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.DefaultImageRaster;
import com.jme3.texture.image.MipMapImageRaster;
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.
*
@ -57,6 +55,8 @@ public class CubeMapWrapper {
private final Vector2f uvs = new Vector2f();
private final Image image;
private final ColorRGBA tmpColor = new ColorRGBA();
/**
* Creates a CubeMapWrapper for the given cube map
* 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.
* @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) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
@ -113,10 +113,26 @@ public class CubeMapWrapper {
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.setMipLevel(mipLevel);
return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
mipMapRaster.setMipLevel(lowerMipLevel);
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.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.*;
import com.jme3.texture.image.ColorSpace;
import com.jme3.ui.Picture;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer;
import java.util.ArrayList;
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 {
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;
// See Peter-Pike Sloan paper for these coefficients
//http://www.ppsloan.org/publications/StupidSH36.pdf
@ -82,7 +82,12 @@ public class EnvMapUtils {
/**
* 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(frontImg.getData(0));
if (leftImg.getEfficentData() != null) {
// 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);
}
cubeImage.setMipMapSizes(rightImg.getMipMapSizes());
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(0);
@ -157,12 +152,7 @@ public class EnvMapUtils {
cubeImage.addData(d.duplicate());
}
if (srcImg.getEfficentData() != null) {
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(srcImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
cubeImage.setMipMapSizes(srcImg.getMipMapSizes());
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
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) {
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)]
@ -254,7 +244,7 @@ public class EnvMapUtils {
float v;
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. */
u = (2.0f * (float) x / ((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
// 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) {
case 0:
store.set(1f, -v, -u);
@ -387,62 +377,21 @@ public class EnvMapUtils {
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) {
return mipLevel==0?1:Math.min(1 << (miptot - 1 + (mipLevel) * 2 ), 8192);
public static int getSampleFromMip(int mipLevel, int miptot) {
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) {
float mipScale = 1.0f;
float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;
return (float) Math.max(0.0, Lod);
return FastMath.sqrt(roughness) * (miptot - 1);
}
/**
@ -482,7 +431,7 @@ public class EnvMapUtils {
float weight;
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();
@ -539,12 +488,6 @@ public class EnvMapUtils {
float yV = texelVect.y;
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 y2 = yV * yV;
float z2 = zV * zV;
@ -558,140 +501,31 @@ public class EnvMapUtils {
shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
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;
}
/**
* {@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);
public static void prepareShCoefs(Vector3f[] shCoefs) {
float coef0 = (1f / (2f * sqrtPi));
float coef1 = -sqrt3Pi / 2f;
float coef2 = -coef1;
float coef3 = coef1;
float coef4 = sqrt15Pi / 2f;
float coef5 = -coef4;
float coef6 = sqrt5Pi / 4f;
float coef7 = coef5;
float coef8 = sqrt15Pi / 4f;
shCoefs[0].multLocal(coef0).multLocal(shBandFactor[0]);
shCoefs[1].multLocal(coef1).multLocal(shBandFactor[1]);
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]);
}
/**
* Generates the Irradiance map (used for image based difuse lighting) from
* Spherical Harmonics coefficients previously computed with
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @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 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++) {
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * irrCubeMap.getImage().getFormat().getBitsPerPixel()/8);
irrCubeMap.getImage().setData(i, buf);
}
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) {
if (store == null) {
@ -719,43 +553,6 @@ public class EnvMapUtils {
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) {
if (store == null) {
store = new Vector3f();
@ -939,9 +736,12 @@ public class EnvMapUtils {
pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear);
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);
targetWrapper.initMipMaps(nbMipMap);
return pem;
}
}

@ -61,26 +61,10 @@ public class LightsDebugState extends BaseAppState {
private Geometry debugGeom;
private Geometry debugBounds;
private Material debugMaterial;
private DebugMode debugMode = DebugMode.PrefilteredEnvMap;
private float probeScale = 1.0f;
private Spatial scene = null;
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
protected void initialize(Application app) {
debugNode = new Node("Environment debug Node");
@ -114,11 +98,7 @@ public class LightsDebugState extends BaseAppState {
Material m = probeGeom.getMaterial();
probeGeom.setLocalScale(probeScale);
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.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
@ -161,24 +141,6 @@ public class LightsDebugState extends BaseAppState {
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

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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -32,7 +32,11 @@
package com.jme3.input;
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.Matrix3f;
import com.jme3.math.Quaternion;
@ -40,12 +44,13 @@ import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
/**
* A first person view camera controller.
* After creation, you must register the camera controller with the
* dispatcher using #registerWithDispatcher().
* A first-person camera controller.
*
* After creation, you (or FlyCamAppState) must register the controller using
* {@link #registerWithInput(com.jme3.input.InputManager)}.
*
* 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
* - WASD keys for moving forward/backward and strafing
* - QZ keys raise or lower the camera
@ -53,41 +58,62 @@ import com.jme3.renderer.Camera;
public class FlyByCamera implements AnalogListener, ActionListener {
private static String[] mappings = new String[]{
CameraInput.FLYCAM_LEFT,
CameraInput.FLYCAM_RIGHT,
CameraInput.FLYCAM_UP,
CameraInput.FLYCAM_DOWN,
CameraInput.FLYCAM_STRAFELEFT,
CameraInput.FLYCAM_STRAFERIGHT,
CameraInput.FLYCAM_FORWARD,
CameraInput.FLYCAM_BACKWARD,
CameraInput.FLYCAM_ZOOMIN,
CameraInput.FLYCAM_ZOOMOUT,
CameraInput.FLYCAM_ROTATEDRAG,
CameraInput.FLYCAM_RISE,
CameraInput.FLYCAM_LOWER,
CameraInput.FLYCAM_INVERTY
};
CameraInput.FLYCAM_LEFT,
CameraInput.FLYCAM_RIGHT,
CameraInput.FLYCAM_UP,
CameraInput.FLYCAM_DOWN,
CameraInput.FLYCAM_STRAFELEFT,
CameraInput.FLYCAM_STRAFERIGHT,
CameraInput.FLYCAM_FORWARD,
CameraInput.FLYCAM_BACKWARD,
CameraInput.FLYCAM_ZOOMIN,
CameraInput.FLYCAM_ZOOMOUT,
CameraInput.FLYCAM_ROTATEDRAG,
CameraInput.FLYCAM_RISE,
CameraInput.FLYCAM_LOWER,
CameraInput.FLYCAM_INVERTY
};
/**
* camera controlled by this controller (not null)
*/
protected Camera cam;
/**
* normalized "up" direction (a unit vector)
*/
protected Vector3f initialUpVec;
/**
* rotation-rate multiplier (1=default)
*/
protected float rotationSpeed = 1f;
/**
* translation speed (in world units per second)
*/
protected float moveSpeed = 3f;
/**
* zoom-rate multiplier (1=default)
*/
protected float zoomSpeed = 1f;
protected MotionAllowedListener motionAllowed = null;
/**
* enable flag for controller (false&rarr;ignoring input)
*/
protected boolean enabled = true;
/**
* drag-to-rotate mode flag
*/
protected boolean dragToRotate = false;
protected boolean canRotate = false;
protected boolean invertY = false;
protected InputManager inputManager;
/**
* Creates a new FlyByCamera to control the given Camera object.
* @param cam
* Creates a new FlyByCamera to control the specified camera.
*
* @param cam camera to be controlled (not null)
*/
public FlyByCamera(Camera cam){
this.cam = cam;
@ -96,10 +122,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
/**
* Sets the up vector that should be used for the camera.
*
* @param upVec
*/
public void setUpVector(Vector3f upVec) {
initialUpVec.set(upVec);
initialUpVec.set(upVec);
}
public void setMotionAllowedListener(MotionAllowedListener listener){
@ -107,56 +134,68 @@ public class FlyByCamera implements AnalogListener, ActionListener {
}
/**
* Sets the move speed. The speed is given in world units per second.
* @param moveSpeed
* Set the translation speed.
*
* @param moveSpeed new speed (in world units per second)
*/
public void setMoveSpeed(float moveSpeed){
this.moveSpeed = moveSpeed;
}
/**
* Gets the move speed. The speed is given in world units per second.
* @return moveSpeed
* Read the translation speed.
*
* @return current speed (in world units per second)
*/
public float getMoveSpeed(){
return moveSpeed;
}
/**
* Sets the rotation speed.
* @param rotationSpeed
* Set the rotation-rate multiplier. The bigger the multiplier, the more
* rotation for a given movement of the mouse.
*
* @param rotationSpeed new rate multiplier (1=default)
*/
public void setRotationSpeed(float rotationSpeed){
this.rotationSpeed = rotationSpeed;
}
/**
* Gets the move speed. The speed is given in world units per second.
* @return rotationSpeed
* Read the rotation-rate multiplier. The bigger the multiplier, the more
* rotation for a given movement of the mouse.
*
* @return current rate multiplier (1=default)
*/
public float getRotationSpeed(){
return rotationSpeed;
}
/**
* Sets the zoom speed.
* @param zoomSpeed
* Set the zoom-rate multiplier. The bigger the multiplier, the more zoom
* for a given movement of the mouse wheel.
*
* @param zoomSpeed new rate multiplier (1=default)
*/
public void setZoomSpeed(float zoomSpeed) {
this.zoomSpeed = zoomSpeed;
}
/**
* Gets the zoom speed. The speed is a multiplier to increase/decrease
* the zoom rate.
* @return zoomSpeed
* Read the zoom-rate multiplier. The bigger the multiplier, the more zoom
* for a given movement of the mouse wheel.
*
* @return current rate multiplier (1=default)
*/
public float getZoomSpeed() {
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){
if (enabled && !enable){
@ -168,32 +207,36 @@ public class FlyByCamera implements AnalogListener, ActionListener {
}
/**
* @return If enabled
* @see FlyByCamera#setEnabled(boolean)
* Test whether this controller is enabled.
*
* @return true if enabled, otherwise false
* @see #setEnabled(boolean)
*/
public boolean isEnabled(){
return enabled;
}
/**
* Test whether drag-to-rotate mode is enabled.
*
* @return If drag to rotate feature is enabled.
*
* @see FlyByCamera#setDragToRotate(boolean)
* @see #setDragToRotate(boolean)
*/
public boolean isDragToRotate() {
return dragToRotate;
}
/**
* Set if drag to rotate mode is enabled.
*
* When true, the user must hold the mouse button
* and drag over the screen to rotate the camera, and the cursor is
* visible until dragged. Otherwise, the cursor is invisible at all times
* and holding the mouse button is not needed to rotate the camera.
* This feature is disabled by default.
*
* @param dragToRotate True if drag to rotate mode is enabled.
* Enable or disable drag-to-rotate mode.
*
* 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 visible
* until dragged. When drag-to-rotate mode is disabled, the cursor is
* invisible at all times and holding the mouse button is not needed to
* rotate the camera. This mode is disabled by default.
*
* @param dragToRotate true to enable, false to disable
*/
public void setDragToRotate(boolean dragToRotate) {
this.dragToRotate = dragToRotate;
@ -203,25 +246,26 @@ public class FlyByCamera implements AnalogListener, ActionListener {
}
/**
* Registers the FlyByCamera to receive input events from the provided
* Dispatcher.
* Register this controller to receive input events from the specified input
* manager.
*
* @param inputManager
*/
public void registerWithInput(InputManager inputManager){
this.inputManager = inputManager;
// both mouse and button - rotation of cam
inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true),
new KeyTrigger(KeyInput.KEY_LEFT));
new KeyTrigger(KeyInput.KEY_LEFT));
inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false),
new KeyTrigger(KeyInput.KEY_RIGHT));
new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false),
new KeyTrigger(KeyInput.KEY_UP));
new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true),
new KeyTrigger(KeyInput.KEY_DOWN));
new KeyTrigger(KeyInput.KEY_DOWN));
// mouse only - zoom in/out with wheel, and rotate drag
inputManager.addMapping(CameraInput.FLYCAM_ZOOMIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
@ -248,43 +292,42 @@ public class FlyByCamera implements AnalogListener, ActionListener {
}
protected void mapJoystick( Joystick joystick ) {
// Map it differently if there are Z axis
if( joystick.getAxis( JoystickAxis.Z_ROTATION ) != null && joystick.getAxis( JoystickAxis.Z_AXIS ) != null ) {
// Make the left stick move
joystick.getXAxis().assignAxis( CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT );
joystick.getYAxis().assignAxis( CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD );
// And the right stick control the camera
// And the right stick control the camera
joystick.getAxis( JoystickAxis.Z_ROTATION ).assignAxis( CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP );
joystick.getAxis( JoystickAxis.Z_AXIS ).assignAxis( CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT );
// And let the dpad be up and down
// And let the dpad be up and down
joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER);
if( joystick.getButton( "Button 8" ) != null ) {
if( joystick.getButton( "Button 8" ) != null ) {
// Let the stanard select button be the y invert toggle
joystick.getButton( "Button 8" ).assignButton( CameraInput.FLYCAM_INVERTY );
}
} else {
}
} else {
joystick.getPovXAxis().assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT);
joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_FORWARD, CameraInput.FLYCAM_BACKWARD);
joystick.getXAxis().assignAxis(CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT);
joystick.getYAxis().assignAxis(CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP);
}
}
}
/**
* Unregisters the FlyByCamera from the event Dispatcher.
* Unregister this controller from its input manager.
*/
public void unregisterInput(){
if (inputManager == null) {
return;
}
for (String s : mappings) {
if (inputManager.hasMapping(s)) {
inputManager.deleteMapping( s );
@ -296,12 +339,16 @@ public class FlyByCamera implements AnalogListener, ActionListener {
Joystick[] joysticks = inputManager.getJoysticks();
if (joysticks != null && joysticks.length > 0){
Joystick joystick = joysticks[0];
// No way to unassing axis
// No way to unassign 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){
if (dragToRotate){
if (canRotate){
@ -329,6 +376,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
cam.setAxes(q);
}
/**
* Zoom the camera by the specified amount.
*
* @param value zoom amount
*/
protected void zoomCamera(float value){
// derive fovY value
float h = cam.getFrustumTop();
@ -338,7 +390,7 @@ public class FlyByCamera implements AnalogListener, ActionListener {
float near = cam.getFrustumNear();
float fovY = FastMath.atan(h / near)
/ (FastMath.DEG_TO_RAD * .5f);
/ (FastMath.DEG_TO_RAD * .5f);
float newFovY = fovY + value * 0.1f * zoomSpeed;
if (newFovY > 0f) {
// Don't let the FOV go zero or negative.
@ -354,6 +406,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
cam.setFrustumRight(w);
}
/**
* Translate the camera upward by the specified amount.
*
* @param value translation amount
*/
protected void riseCamera(float value){
Vector3f vel = new Vector3f(0, value * moveSpeed, 0);
Vector3f pos = cam.getLocation().clone();
@ -366,6 +423,12 @@ public class FlyByCamera implements AnalogListener, ActionListener {
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){
Vector3f vel = new Vector3f();
Vector3f pos = cam.getLocation().clone();
@ -385,6 +448,14 @@ public class FlyByCamera implements AnalogListener, ActionListener {
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) {
if (!enabled)
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) {
if (!enabled)
return;
@ -424,11 +503,10 @@ public class FlyByCamera implements AnalogListener, ActionListener {
canRotate = value;
inputManager.setCursorVisible(!value);
} else if (name.equals(CameraInput.FLYCAM_INVERTY)) {
// Toggle on the up.
if( !value ) {
// Invert the "up" direction.
if( !value ) {
invertY = !invertY;
}
}
}
}
}

@ -50,17 +50,19 @@ import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import com.jme3.util.TempVars;
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.
* 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.
* There are two environment maps held by the LightProbe :
* - The irradiance map (used for indirect diffuse lighting in the PBR pipeline).
* There are two environment data structure held by the LightProbe :
* - 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).
* Note that when instanciating the LightProbe, both those maps are null.
* To render them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* Note that when instantiating the LightProbe, both of those structures are null.
* To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* 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).
@ -75,12 +77,15 @@ import java.io.IOException;
*/
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 BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
private boolean ready = false;
private Vector3f position = new Vector3f();
private Node debugNode;
private int nbMipMaps;
/**
* Empty constructor used for serialization.
@ -89,23 +94,6 @@ public class LightProbe extends Light implements Savable {
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
* 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 {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(irradianceMap, "irradianceMap", null);
oc.write(shCoeffs, "shCoeffs", null);
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
oc.write(position, "position", null);
oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
oc.write(ready, "ready", false);
oc.write(nbMipMaps, "nbMipMaps", 0);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", 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));
nbMipMaps = ic.readInt("nbMipMaps", 0);
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];
}
}
}
/**
@ -194,14 +195,11 @@ public class LightProbe extends Light implements Savable {
*/
public Node getDebugGui(AssetManager manager) {
if (!ready) {
throw new UnsupportedOperationException("This EnvProbeis not ready yet, try to test isReady()");
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
}
if (debugNode == null) {
debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager);
debugNode.attachChild(debugIrrCm);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);
}
@ -209,6 +207,14 @@ public class LightProbe extends Light implements Savable {
return debugNode;
}
public Vector3f[] getShCoeffs() {
return shCoeffs;
}
public void setShCoeffs(Vector3f[] shCoeffs) {
this.shCoeffs = shCoeffs;
}
/**
* Returns the position of the LightProbe in world space
* @return the wolrd space position
@ -226,6 +232,14 @@ public class LightProbe extends Light implements Savable {
getBounds().setCenter(position);
}
public int getNbMipMaps() {
return nbMipMaps;
}
public void setNbMipMaps(int nbMipMaps) {
this.nbMipMaps = nbMipMaps;
}
@Override
public boolean intersectsBox(BoundingBox box, TempVars vars) {
return getBounds().intersectsBoundingBox(box);

@ -37,6 +37,7 @@ import com.jme3.math.*;
import com.jme3.shader.VarType;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.io.IOException;
/**
@ -301,20 +302,21 @@ When arrays can be inserted in J3M files
OutputCapsule oc = ex.getCapsule(this);
oc.write(type, "varType", null);
oc.write(name, "name", null);
if (value == null) {
return;
}
if (value instanceof Savable) {
Savable s = (Savable) value;
oc.write(s, "value_savable", null);
oc.write((Savable) value, "value_savable", null);
} else if (value instanceof Float) {
Float f = (Float) value;
oc.write(f.floatValue(), "value_float", 0f);
oc.write((Float) value, "value_float", 0f);
} else if (value instanceof Integer) {
Integer i = (Integer) value;
oc.write(i.intValue(), "value_int", 0);
oc.write((Integer) value, "value_int", 0);
} else if (value instanceof Boolean) {
Boolean b = (Boolean) value;
oc.write(b.booleanValue(), "value_bool", false);
oc.write((Boolean) value, "value_bool", false);
} 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
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) {
clone.attributes.add(attribute.clone());
@ -200,8 +206,9 @@ public class ShaderGenerationInfo implements Savable, Cloneable {
clone.vertexUniforms.add(uniform.clone());
}
clone.vertexGlobal = vertexGlobal.clone();
if (vertexGlobal != null) {
clone.vertexGlobal = vertexGlobal.clone();
}
for (ShaderNodeVariable varying : varyings) {
clone.varyings.add(varying.clone());

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

@ -115,7 +115,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
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");
lightProbe = null;
@ -128,16 +130,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
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){
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
int irrUnit = lastTexUnit++;
int pemUnit = lastTexUnit++;
rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
lightProbeIrrMap.setValue(VarType.Int, irrUnit);
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit);
} 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.
* @return This matrix, after combining.
*/
public Transform combineWithParent(Transform parent) {
//applying parent scale to local scale
scale.multLocal(parent.scale);
// rot.multLocal(parent.rot);
//applying parent rotation to local rotation.
parent.rot.mult(rot, rot);
// This here, is evil code
// parent
// .rot
// .multLocal(translation)
// .multLocal(parent.scale)
// .addLocal(parent.translation);
//applying parent scale to local translation.
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
.rot
.multLocal(translation)
.addLocal(parent.translation);
return this;
}

@ -45,10 +45,6 @@ public class OpenCLObjectManager {
private static final Logger LOG = Logger.getLogger(OpenCLObjectManager.class.getName());
private static final Level LOG_LEVEL1 = Level.FINER;
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 OpenCLObjectManager() {}
@ -59,7 +55,6 @@ public class OpenCLObjectManager {
private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
private HashSet<OpenCLObjectRef> activeObjects = new HashSet<OpenCLObjectRef>();
private int gcCounter = 0;
private static class OpenCLObjectRef extends PhantomReference<Object> {
@ -80,6 +75,7 @@ public class OpenCLObjectManager {
private void deleteObject(OpenCLObjectRef ref) {
LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser);
ref.releaser.release();
ref.clear();
activeObjects.remove(ref);
}
@ -89,15 +85,6 @@ public class OpenCLObjectManager {
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;
while (true) {
// Remove objects reclaimed by GC.
@ -117,6 +104,7 @@ public class OpenCLObjectManager {
for (OpenCLObjectRef ref : activeObjects) {
LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser);
ref.releaser.release();
ref.clear();
}
activeObjects.clear();
}

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

@ -66,7 +66,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
private FrameBuffer renderFrameBuffer;
private Texture2D filterTexture;
private Texture2D depthTexture;
private SafeArrayList<Filter> filters = new SafeArrayList<Filter>(Filter.class);
private SafeArrayList<Filter> filters = new SafeArrayList<>(Filter.class);
private AssetManager assetManager;
private Picture fsQuad;
private boolean computeDepth = false;
@ -85,6 +85,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
private AppProfiler prof;
private Format fbFormat = Format.RGB111110F;
private Format depthFormat = Format.Depth;
/**
* Create a FilterProcessor
@ -143,9 +144,13 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
fsQuad = new Picture("filter full screen quad");
fsQuad.setWidth(1);
fsQuad.setHeight(1);
if (fbFormat == Format.RGB111110F && !renderer.getCaps().contains(Caps.PackedFloatTexture)) {
fbFormat = Format.RGB8;
if (!renderer.getCaps().contains(Caps.PackedFloatTexture)) {
if (!renderer.getCaps().contains(Caps.FloatTexture)) {
fbFormat = Format.RGB8;
} else {
fbFormat = Format.RGB16F;
}
}
Camera cam = vp.getCamera();
@ -161,6 +166,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
reshape(vp, cam.getWidth(), cam.getHeight());
}
public Format getDefaultPassTextureFormat() {
return fbFormat;
}
/**
* init the given filter
* @param filter
@ -170,7 +179,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
filter.setProcessor(this);
if (filter.isRequiresDepthTexture()) {
if (!computeDepth && renderFrameBuffer != null) {
depthTexture = new Texture2D(width, height, Format.Depth24);
depthTexture = new Texture2D(width, height, depthFormat);
renderFrameBuffer.setDepthTexture(depthTexture);
}
computeDepth = true;
@ -469,20 +478,20 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
renderFrameBufferMS = new FrameBuffer(width, height, numSamples);
if (caps.contains(Caps.OpenGL32)) {
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.setColorTexture(msColor);
filterTexture = msColor;
depthTexture = msDepth;
} else {
renderFrameBufferMS.setDepthBuffer(Format.Depth);
renderFrameBufferMS.setDepthBuffer(depthFormat);
renderFrameBufferMS.setColorBuffer(fbFormat);
}
}
if (numSamples <= 1 || !caps.contains(Caps.OpenGL32)) {
renderFrameBuffer = new FrameBuffer(width, height, 1);
renderFrameBuffer.setDepthBuffer(Format.Depth);
renderFrameBuffer.setDepthBuffer(depthFormat);
filterTexture = new Texture2D(width, height, fbFormat);
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
* similar to constructing a new camera, but reusing the same Object. This
* method is called by an associated {@link RenderManager} to notify the camera of
* changes in the display dimensions.
* Resize this camera's view for the specified display size. Invoked by an
* associated {@link RenderManager} to notify the camera of changes to the
* display dimensions.
*
* @param width the view width
* @param height the view height
* @param fixAspect If true, the camera's aspect ratio will be recomputed.
* Recomputing the aspect ratio requires changing the frustum values.
* @param width the new width of the display, in pixels
* @param height the new height of the display, in pixels
* @param fixAspect if true, recompute the camera's frustum to preserve its
* prior aspect ratio
*/
public void resize(int width, int height, boolean fixAspect) {
this.width = width;
this.height = height;
onViewPortChange();
if (fixAspect /*&& !parallelProjection*/) {
frustumRight = frustumTop * ((float) width / height);
if (fixAspect) {
float h = height * (viewPortTop - viewPortBottom);
float w = width * (viewPortRight - viewPortLeft);
float aspectRatio = w / h;
frustumRight = frustumTop * aspectRatio;
frustumLeft = -frustumRight;
onFrustumChange();
}

@ -128,6 +128,26 @@ public enum Caps {
* Supports OpenGL 4.0
*/
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.
*
@ -174,6 +194,26 @@ public enum Caps {
* Supports GLSL 4.0
*/
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.
*/
@ -486,6 +526,18 @@ public enum Caps {
if (!caps.contains(Caps.GLSL150)) return false;
case 330:
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:
return false;
}

@ -50,6 +50,7 @@ public interface GL {
public static final int GL_ARRAY_BUFFER = 0x8892;
public static final int GL_BACK = 0x405;
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_CLAMP_TO_EDGE = 0x812F;
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_POSITIVE_Z = 0x8519;
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_MAX_LEVEL = 0x813D;
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_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_UNDERFLOW = 0x504;
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_TEXTURE_COMPARE_FUNC = 0x884D;
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 glBindVertexArray(int param1); /// 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 String glGetString(int param1, int param2); /// GL3+
}

@ -95,12 +95,6 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
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) {
gl.glBlendEquationSeparate(colorMode, alphaMode);
checkError();

@ -598,8 +598,15 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
return sync;
}
@Override
public void glBlendEquationSeparate(int colorMode, int alphaMode) {
gl.glBlendEquationSeparate(colorMode, alphaMode);
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_EXT = 0x8DB9;
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_LUMINANCE32F_ARB = 0x8818;
public static final int GL_LUMINANCE_ALPHA16F_ARB = 0x881F;

@ -89,6 +89,7 @@ public interface GLFbo {
public void glDeleteRenderbuffersEXT(IntBuffer param1);
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 glFramebufferTextureLayerEXT(int target, int attachment, int texture, int level, int layer);
public void glGenFramebuffersEXT(IntBuffer param1);
public void glGenRenderbuffersEXT(IntBuffer param1);
public void glGenerateMipmapEXT(int param1);

@ -103,14 +103,19 @@ public final class GLImageFormats {
public static GLImageFormat[][] getFormatsForCaps(EnumSet<Caps> caps) {
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+)
if (caps.contains(Caps.CoreProfile)) {
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.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.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.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.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 {
// Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above..
if (!caps.contains(Caps.CoreProfile)) {
@ -182,33 +192,38 @@ public final class GLImageFormats {
if (caps.contains(Caps.FloatTexture)) {
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.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.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);
}
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);
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)) {
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)) {
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
// 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);
// 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)) {
format(formatToGL, Format.Depth24, GL2.GL_DEPTH_COMPONENT24, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT);
}

@ -180,23 +180,38 @@ public final class GLRenderer implements Renderer {
caps.add(Caps.OpenGL20);
if (oglVer >= 210) {
caps.add(Caps.OpenGL21);
if (oglVer >= 300) {
caps.add(Caps.OpenGL30);
if (oglVer >= 310) {
caps.add(Caps.OpenGL31);
if (oglVer >= 320) {
caps.add(Caps.OpenGL32);
}
if (oglVer >= 330) {
caps.add(Caps.OpenGL33);
caps.add(Caps.GeometryShader);
}
if (oglVer >= 400) {
caps.add(Caps.OpenGL40);
caps.add(Caps.TesselationShader);
}
}
}
}
if (oglVer >= 300) {
caps.add(Caps.OpenGL30);
}
if (oglVer >= 310) {
caps.add(Caps.OpenGL31);
}
if (oglVer >= 320) {
caps.add(Caps.OpenGL32);
}
if (oglVer >= 330) {
caps.add(Caps.OpenGL33);
caps.add(Caps.GeometryShader);
}
if (oglVer >= 400) {
caps.add(Caps.OpenGL40);
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
// 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:
caps.add(Caps.GLSL400);
case 330:
@ -1556,7 +1581,7 @@ public final class GLRenderer implements Renderer {
image.getId(),
0);
} else {
gl3.glFramebufferTextureLayer(GLFbo.GL_FRAMEBUFFER_EXT,
glfbo.glFramebufferTextureLayerEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
image.getId(),
0,
@ -1955,7 +1980,13 @@ public final class GLRenderer implements Renderer {
@SuppressWarnings("fallthrough")
private void setupTextureParams(int unit, Texture tex) {
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;
if (image != null) {
@ -2158,41 +2189,48 @@ public final class GLRenderer implements Renderer {
int target = convertTextureType(type, img.getMultiSamples(), -1);
bindTextureAndUnit(target, img, unit);
if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
// Image does not have mipmaps, but they are required.
// Generate from base level.
int imageSamples = img.getMultiSamples();
if (!caps.contains(Caps.FrameBuffer) && gl2 != null) {
gl2.glTexParameteri(target, GL2.GL_GENERATE_MIPMAP, GL.GL_TRUE);
img.setMipmapsGenerated(true);
} else {
// For OpenGL3 and up.
// We'll generate mipmaps via glGenerateMipmapEXT (see below)
if (imageSamples <= 1) {
if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
// Image does not have mipmaps, but they are required.
// Generate from base level.
if (!caps.contains(Caps.FrameBuffer) && gl2 != null) {
gl2.glTexParameteri(target, GL2.GL_GENERATE_MIPMAP, GL.GL_TRUE);
img.setMipmapsGenerated(true);
} else {
// For OpenGL3 and up.
// We'll generate mipmaps via glGenerateMipmapEXT (see below)
}
} else if (caps.contains(Caps.OpenGL20)) {
if (img.hasMipmaps()) {
// Image already has mipmaps, set the max level based on the
// number of mipmaps we have.
gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
} else {
// Image does not have mipmaps and they are not required.
// Specify that that the texture has no mipmaps.
gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, 0);
}
}
} else if (img.hasMipmaps()) {
// Image already has mipmaps, set the max level based on the
// number of mipmaps we have.
gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
} else {
// Image does not have mipmaps and they are not required.
// Specify that that the texture has no mipmaps.
gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, 0);
}
// 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()) {
img.setMultiSamples(Math.min(limits.get(Limits.DepthTextureSamples), imageSamples));
} else {
img.setMultiSamples(Math.min(limits.get(Limits.ColorTextureSamples), imageSamples));
}
}
// Check if graphics card doesn't support multisample textures
if (!caps.contains(Caps.TextureMultisample)) {
if (img.getMultiSamples() > 1) {
throw new RendererException("Multisample textures are not supported by the video hardware");
}
scaleToPot = false;
}
// Check if graphics card doesn't support depth textures
@ -2567,6 +2605,7 @@ public final class GLRenderer implements Renderer {
}
switch (indexBuf.getFormat()) {
case UnsignedByte:
case UnsignedShort:
// OK: Works on all platforms.
break;

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

@ -32,7 +32,6 @@
package com.jme3.renderer.opengl;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderContext;
import com.jme3.renderer.RendererException;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
@ -91,7 +90,7 @@ final class TextureUtil {
public GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) {
//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);
if (glFmt == null && isSrgb) {
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_A, GL.GL_GREEN);
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;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetManager;
import com.jme3.asset.ModelKey;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.util.clone.Cloner;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
@ -164,25 +163,24 @@ public class AssetLinkNode extends Node {
@Override
public void read(JmeImporter e) throws IOException {
super.read(e);
InputCapsule capsule = e.getCapsule(this);
BinaryImporter importer = BinaryImporter.getInstance();
AssetManager loaderManager = e.getAssetManager();
final InputCapsule capsule = e.getCapsule(this);
final AssetManager assetManager = e.getAssetManager();
assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>());
for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
ModelKey modelKey = it.next();
AssetInfo info = loaderManager.locateAsset(modelKey);
Spatial child = null;
if (info != null) {
child = (Spatial) importer.load(info);
}
for (final Iterator<ModelKey> iterator = assetLoaderKeys.iterator(); iterator.hasNext(); ) {
final ModelKey modelKey = iterator.next();
final Spatial child = assetManager.loadAsset(modelKey);
if (child != null) {
child.parent = this;
children.add(child);
assetChildren.put(modelKey, child);
} else {
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
new Object[]{ modelKey, key });
Logger.getLogger(this.getClass().getName()).log(Level.WARNING,
"Cannot locate {0} for asset link node {1}", new Object[]{modelKey, key});
}
}
}
@ -190,7 +188,7 @@ public class AssetLinkNode extends Node {
@Override
public void write(JmeExporter e) throws IOException {
SafeArrayList<Spatial> childs = children;
children = new SafeArrayList<Spatial>(Spatial.class);
children = new SafeArrayList<>(Spatial.class);
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
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 added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
* To integrate them in the batch you have to call the batch() method again on the batchNode.
*
* TODO normal or tangents or both looks a bit weird
* <p>
* TODO more automagic (batch when needed in the updateLogicalState)
*
* @author Nehon
*/
public class BatchNode extends GeometryGroupNode {
@ -108,7 +108,7 @@ public class BatchNode extends GeometryGroupNode {
public void onMaterialChange(Geometry geom) {
throw new UnsupportedOperationException(
"Cannot set the material of a batched geometry, "
+ "change the material of the parent BatchNode.");
+ "change the material of the parent BatchNode.");
}
@Override
@ -122,7 +122,7 @@ public class BatchNode extends GeometryGroupNode {
setNeedsFullRebatch(true);
}
protected Matrix4f getTransformMatrix(Geometry g){
protected Matrix4f getTransformMatrix(Geometry g) {
return g.cachedWorldMat;
}
@ -133,35 +133,44 @@ public class BatchNode extends GeometryGroupNode {
Mesh origMesh = bg.getMesh();
VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer posBuf = (FloatBuffer) pvb.getData();
VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer normBuf = (FloatBuffer) nvb.getData();
VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
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);
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);
FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent);
FloatBuffer otanBuf = (FloatBuffer) otvb.getData();
doTransformsTangents(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
if (nvb != null) {
nvb.updateData(normBuf);
}
if (tvb != null) {
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();
}
}
private FloatBuffer getFloatBuffer(VertexBuffer vb) {
if (vb == null) {
return null;
}
return (FloatBuffer) vb.getData();
}
/**
* 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
@ -234,7 +243,7 @@ public class BatchNode extends GeometryGroupNode {
logger.log(Level.FINE, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
//init the temp arrays if something has been batched only.
if(matMap.size()>0){
if (matMap.size() > 0) {
//TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed.
//init temp float arrays
tmpFloat = new float[maxVertCount * 3];
@ -257,6 +266,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* recursively visit the subgraph and unbatch geometries
*
* @param 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
*
* <p>
* use getMaterial(Material material,int batchIndex) to get a material from a specific batch
*
* @return the material that is used for the first batch of this BatchNode
*
* @see #setMaterial(com.jme3.material.Material)
*/
public Material getMaterial() {
@ -428,14 +437,6 @@ public class BatchNode extends GeometryGroupNode {
+ " primitive types: " + 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;
}
@ -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) {
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) {
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;
@ -588,60 +543,76 @@ public class BatchNode extends GeometryGroupNode {
int offset = start * 3;
int tanOffset = start * 4;
bindBufPos.rewind();
bindBufNorm.rewind();
bindBufTangents.rewind();
bindBufPos.get(tmpFloat, 0, length);
bindBufNorm.get(tmpFloatN, 0, length);
bindBufTangents.get(tmpFloatT, 0, tanLength);
if (bindBufNorm != null) {
bindBufNorm.rewind();
bindBufNorm.get(tmpFloatN, 0, length);
}
if (bindBufTangents != null) {
bindBufTangents.rewind();
bindBufTangents.get(tmpFloatT, 0, tanLength);
}
int index = 0;
int tanIndex = 0;
while (index < length) {
pos.x = tmpFloat[index];
norm.x = tmpFloatN[index++];
pos.y = tmpFloat[index];
norm.y = tmpFloatN[index++];
pos.z = tmpFloat[index];
norm.z = tmpFloatN[index];
int index1, index2, tanIndex1, tanIndex2;
tan.x = tmpFloatT[tanIndex++];
tan.y = tmpFloatT[tanIndex++];
tan.z = tmpFloatT[tanIndex++];
while (index < length) {
index1 = index + 1;
index2 = index + 2;
pos.x = tmpFloat[index];
pos.y = tmpFloat[index1];
pos.z = tmpFloat[index2];
transform.mult(pos, pos);
transform.multNormal(norm, norm);
transform.multNormal(tan, tan);
index -= 2;
tanIndex -= 3;
tmpFloat[index] = pos.x;
tmpFloatN[index++] = norm.x;
tmpFloat[index] = pos.y;
tmpFloatN[index++] = norm.y;
tmpFloat[index] = pos.z;
tmpFloatN[index++] = norm.z;
tmpFloatT[tanIndex++] = tan.x;
tmpFloatT[tanIndex++] = tan.y;
tmpFloatT[tanIndex++] = tan.z;
tmpFloat[index1] = pos.y;
tmpFloat[index2] = pos.z;
if (bindBufNorm != null) {
norm.x = tmpFloatN[index];
norm.y = tmpFloatN[index1];
norm.z = tmpFloatN[index2];
transform.multNormal(norm, norm);
tmpFloatN[index] = norm.x;
tmpFloatN[index1] = norm.y;
tmpFloatN[index2] = norm.z;
}
//Skipping 4th element of tangent buffer (handedness)
tanIndex++;
index += 3;
if (bindBufTangents != null) {
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();
bufPos.position(offset);
//using bulk put as it's faster
bufPos.position(offset);
bufPos.put(tmpFloat, 0, length);
bufNorm.position(offset);
//using bulk put as it's faster
bufNorm.put(tmpFloatN, 0, length);
bufTangents.position(tanOffset);
//using bulk put as it's faster
bufTangents.put(tmpFloatT, 0, tanLength);
if (bindBufNorm != null) {
bufNorm.position(offset);
bufNorm.put(tmpFloatN, 0, length);
}
if (bindBufTangents != null) {
bufTangents.position(tanOffset);
bufTangents.put(tmpFloatT, 0, tanLength);
}
}
private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
@ -653,11 +624,11 @@ public class BatchNode extends GeometryGroupNode {
offset *= componentSize;
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.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 + 2, pos.z);
}
@ -667,6 +638,7 @@ public class BatchNode extends GeometryGroupNode {
protected class Batch implements JmeCloneable {
/**
* update the batchesByGeom map for this batch with the given List of geometries
*
* @param list
*/
void updateGeomList(List<Geometry> list) {
@ -676,6 +648,7 @@ public class BatchNode extends GeometryGroupNode {
}
}
}
Geometry geometry;
public final Geometry getGeometry() {
@ -685,14 +658,14 @@ public class BatchNode extends GeometryGroupNode {
@Override
public Batch jmeClone() {
try {
return (Batch)super.clone();
return (Batch) super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
public void cloneFields(Cloner cloner, Object original) {
this.geometry = cloner.clone(geometry);
}
@ -704,11 +677,11 @@ public class BatchNode extends GeometryGroupNode {
@Override
public Node clone(boolean cloneMaterials) {
BatchNode clone = (BatchNode)super.clone(cloneMaterials);
if ( batches.size() > 0) {
for ( Batch b : batches ) {
for ( int i =0; i < clone.children.size(); i++ ) {
if ( clone.children.get(i).getName().equals(b.geometry.getName())) {
BatchNode clone = (BatchNode) super.clone(cloneMaterials);
if (batches.size() > 0) {
for (Batch b : batches) {
for (int i = 0; i < clone.children.size(); i++) {
if (clone.children.get(i).getName().equals(b.geometry.getName())) {
clone.children.remove(i);
break;
}
@ -723,10 +696,10 @@ public class BatchNode extends GeometryGroupNode {
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
public void cloneFields(Cloner cloner, Object original) {
super.cloneFields(cloner, original);
this.batches = cloner.clone(batches);
@ -736,7 +709,7 @@ public class BatchNode extends GeometryGroupNode {
HashMap<Geometry, Batch> newBatchesByGeom = new HashMap<Geometry, Batch>();
for( Map.Entry<Geometry, Batch> e : batchesByGeom.entrySet() ) {
for (Map.Entry<Geometry, Batch> e : batchesByGeom.entrySet()) {
newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
}
this.batchesByGeom = newBatchesByGeom;
@ -745,7 +718,7 @@ public class BatchNode extends GeometryGroupNode {
@Override
public int collideWith(Collidable other, CollisionResults results) {
int total = 0;
for (Spatial child : children.getArray()){
for (Spatial child : children.getArray()) {
if (!isBatch(child)) {
total += child.collideWith(other, results);
}

@ -1,122 +1,123 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.renderer.Camera;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
* <code>CameraNode</code> simply uses {@link CameraControl} to implement
* linking of camera and node data.
*
* @author Tim8Dev
*/
public class CameraNode extends Node {
private CameraControl camControl;
/**
* Serialization only. Do not use.
*/
public CameraNode() {
}
public CameraNode(String name, Camera camera) {
this(name, new CameraControl(camera));
}
public CameraNode(String name, CameraControl control) {
super(name);
addControl(control);
camControl = control;
}
public void setEnabled(boolean enabled) {
camControl.setEnabled(enabled);
}
public boolean isEnabled() {
return camControl.isEnabled();
}
public void setControlDir(ControlDirection controlDir) {
camControl.setControlDir(controlDir);
}
public void setCamera(Camera camera) {
camControl.setCamera(camera);
}
public ControlDirection getControlDir() {
return camControl.getControlDir();
}
public Camera getCamera() {
return camControl.getCamera();
}
// @Override
// public void lookAt(Vector3f position, Vector3f upVector) {
// this.lookAt(position, upVector);
// camControl.getCamera().lookAt(position, upVector);
// }
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// A change in behavior... I think previously CameraNode was probably
// not really cloneable... or at least its camControl would be pointing
// to the wrong control. -pspeed
this.camControl = cloner.clone(camControl);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
camControl = (CameraControl)im.getCapsule(this).readSavable("camControl", null);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
ex.getCapsule(this).write(camControl, "camControl", null);
}
}
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.renderer.Camera;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
* <code>CameraNode</code> simply uses {@link CameraControl} to implement
* linking of camera and node data.
*
* @author Tim8Dev
*/
public class CameraNode extends Node {
private CameraControl camControl;
/**
* Serialization only. Do not use.
*/
public CameraNode() {
super();
}
public CameraNode(String name, Camera camera) {
this(name, new CameraControl(camera));
}
public CameraNode(String name, CameraControl control) {
super(name);
addControl(control);
camControl = control;
}
public void setEnabled(boolean enabled) {
camControl.setEnabled(enabled);
}
public boolean isEnabled() {
return camControl.isEnabled();
}
public void setControlDir(ControlDirection controlDir) {
camControl.setControlDir(controlDir);
}
public void setCamera(Camera camera) {
camControl.setCamera(camera);
}
public ControlDirection getControlDir() {
return camControl.getControlDir();
}
public Camera getCamera() {
return camControl.getCamera();
}
// @Override
// public void lookAt(Vector3f position, Vector3f upVector) {
// this.lookAt(position, upVector);
// camControl.getCamera().lookAt(position, upVector);
// }
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// A change in behavior... I think previously CameraNode was probably
// not really cloneable... or at least its camControl would be pointing
// to the wrong control. -pspeed
this.camControl = cloner.clone(camControl);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
camControl = (CameraControl)im.getCapsule(this).readSavable("camControl", null);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
ex.getCapsule(this).write(camControl, "camControl", null);
}
}

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

@ -53,6 +53,7 @@ import com.jme3.util.IntMap.Entry;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.*;
import java.util.ArrayList;
@ -331,6 +332,15 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
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},
* and {@link Type#BindPoseTangent}
@ -338,51 +348,48 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* buffers already set on the mesh.
* This method does nothing if the mesh has no bone weight or index
* buffers.
*
* @param forSoftwareAnim Should be true if the bind pose is to be generated.
*/
public void generateBindPose(boolean forSoftwareAnim){
if (forSoftwareAnim){
VertexBuffer pos = getBuffer(Type.Position);
if (pos == null || getBuffer(Type.BoneIndex) == null) {
// ignore, this mesh doesn't have positional data
// or it doesn't have bone-vertex assignments, so its not animated
return;
}
VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
bindPos.setupData(Usage.CpuOnly,
pos.getNumComponents(),
pos.getFormat(),
BufferUtils.clone(pos.getData()));
setBuffer(bindPos);
// XXX: note that this method also sets stream mode
// so that animation is faster. this is not needed for hardware skinning
pos.setUsage(Usage.Stream);
VertexBuffer norm = getBuffer(Type.Normal);
if (norm != null) {
VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
bindNorm.setupData(Usage.CpuOnly,
norm.getNumComponents(),
norm.getFormat(),
BufferUtils.clone(norm.getData()));
setBuffer(bindNorm);
norm.setUsage(Usage.Stream);
}
public void generateBindPose() {
VertexBuffer pos = getBuffer(Type.Position);
if (pos == null || getBuffer(Type.BoneIndex) == null) {
// ignore, this mesh doesn't have positional data
// or it doesn't have bone-vertex assignments, so its not animated
return;
}
VertexBuffer tangents = getBuffer(Type.Tangent);
if (tangents != null) {
VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
bindTangents.setupData(Usage.CpuOnly,
tangents.getNumComponents(),
tangents.getFormat(),
BufferUtils.clone(tangents.getData()));
setBuffer(bindTangents);
tangents.setUsage(Usage.Stream);
}// else hardware setup does nothing, mesh already in bind pose
VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
bindPos.setupData(Usage.CpuOnly,
pos.getNumComponents(),
pos.getFormat(),
BufferUtils.clone(pos.getData()));
setBuffer(bindPos);
// XXX: note that this method also sets stream mode
// so that animation is faster. this is not needed for hardware skinning
pos.setUsage(Usage.Stream);
VertexBuffer norm = getBuffer(Type.Normal);
if (norm != null) {
VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
bindNorm.setupData(Usage.CpuOnly,
norm.getNumComponents(),
norm.getFormat(),
BufferUtils.clone(norm.getData()));
setBuffer(bindNorm);
norm.setUsage(Usage.Stream);
}
VertexBuffer tangents = getBuffer(Type.Tangent);
if (tangents != null) {
VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
bindTangents.setupData(Usage.CpuOnly,
tangents.getNumComponents(),
tangents.getFormat(),
BufferUtils.clone(tangents.getData()));
setBuffer(bindTangents);
tangents.setUsage(Usage.Stream);
}// else hardware setup does nothing, mesh already in bind pose
}
/**
@ -396,11 +403,20 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
// convert indices to ubytes on the heap
VertexBuffer indices = getBuffer(Type.BoneIndex);
if (!indices.getData().hasArray()) {
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
originalIndex.clear();
arrayIndex.put(originalIndex);
indices.updateData(arrayIndex);
if (indices.getFormat() == Format.UnsignedByte) {
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
originalIndex.clear();
arrayIndex.put(originalIndex);
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);
@ -429,13 +445,24 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
//if HWBoneIndex and HWBoneWeight are empty, we setup them as direct
//buffers with software anim buffers data
VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex);
Buffer result;
if (indicesHW.getData() == null) {
VertexBuffer indices = getBuffer(Type.BoneIndex);
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer directIndex = BufferUtils.createByteBuffer(originalIndex.capacity());
originalIndex.clear();
directIndex.put(originalIndex);
indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex);
if (indices.getFormat() == Format.UnsignedByte) {
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer directIndex = BufferUtils.createByteBuffer(originalIndex.capacity());
originalIndex.clear();
directIndex.put(originalIndex);
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);
@ -986,6 +1013,18 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
BoundingVolume worldBound,
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) {
return 0;
}
@ -1420,7 +1459,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
return false; // no bone animation data
}
ByteBuffer boneIndexBuffer = (ByteBuffer) biBuf.getData();
IndexBuffer boneIndexBuffer = IndexBuffer.wrapIndexBuffer(biBuf.getData());
boneIndexBuffer.rewind();
int numBoneIndices = boneIndexBuffer.remaining();
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.
*/
byte biByte = (byte) boneIndex; // bone indices wrap after 127
int biByte = boneIndex;
for (int vIndex = 0; vIndex < numVertices; vIndex++) {
for (int wIndex = 0; wIndex < 4; wIndex++) {
byte bIndex = boneIndexBuffer.get();
int bIndex = boneIndexBuffer.get();
float weight = weightBuffer.get();
if (wIndex < maxNumWeights && bIndex == biByte && weight != 0f) {
return true;

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

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

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

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

@ -75,7 +75,29 @@ public abstract class IndexBuffer {
return new IndexShortBuffer(BufferUtils.createShortBuffer(indexCount));
}
}
/**
* @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.
*

@ -47,7 +47,12 @@ public class IndexByteBuffer extends IndexBuffer {
buf = buffer;
buf.rewind();
}
@Override
public int get() {
return buf.get() & 0x000000FF;
}
@Override
public int get(int i) {
return buf.get(i) & 0x000000FF;

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

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

@ -55,6 +55,7 @@ public class VirtualIndexBuffer extends IndexBuffer {
protected int numVerts = 0;
protected int numIndices = 0;
protected Mode meshMode;
protected int position = 0;
public VirtualIndexBuffer(int numVerts, Mode meshMode){
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
public int get(int i) {
if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){

@ -50,7 +50,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* 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 ShaderNodeVariable inPosTmp;
protected ShaderNodeVariable inPosTmp;
/**
* creates a Glsl100ShaderGenerator
@ -110,7 +110,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
protected void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type) {
source.append("\n");
for (ShaderNodeVariable var : info.getVaryings()) {
declareVarying(source, var, type == ShaderType.Vertex ? false : true);
declareVarying(source, var, type != ShaderType.Vertex);
}
}
@ -141,7 +141,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
@Override
protected void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type) {
source.append("\n");
source.append("void main(){\n");
source.append("void main() {\n");
indent();
appendIndent(source);
if (type == ShaderType.Vertex) {
@ -211,7 +211,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* this methods does things in this order :
*
* 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
* variables, the name space is appended with a "_" before the variable name
* in the code to avoid names collision between shaderNodes. <br>
@ -237,27 +237,59 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
comment(source, shaderNode, "Begin");
startCondition(shaderNode.getCondition(), source);
List<String> declaredInputs = new ArrayList<String>();
final List<String> declaredInputs = new ArrayList<>();
for (VariableMapping mapping : shaderNode.getInputMapping()) {
//all variables fed with a matparam or world param are replaced but the matparam itself
//it avoids issue with samplers that have to be uniforms, and it optimize a but the shader code.
if (isWorldOrMaterialParam(mapping.getRightVariable())) {
nodeSource = replace(nodeSource, mapping.getLeftVariable(), mapping.getRightVariable().getPrefix() + mapping.getRightVariable().getName());
final ShaderNodeVariable rightVariable = mapping.getRightVariable();
final ShaderNodeVariable leftVariable = mapping.getLeftVariable();
//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 {
if (mapping.getLeftVariable().getType().startsWith("sampler")) {
if (leftVariable.getType().startsWith("sampler")) {
throw new IllegalArgumentException("a Sampler must be a uniform");
}
map(mapping, source);
String newName = shaderNode.getName() + "_" + mapping.getLeftVariable().getName();
if (!declaredInputs.contains(newName)) {
nodeSource = replace(nodeSource, mapping.getLeftVariable(), newName);
declaredInputs.add(newName);
}
}
String newName = shaderNode.getName() + "_" + leftVariable.getName();
if (!declaredInputs.contains(newName)) {
nodeSource = replace(nodeSource, leftVariable, 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());
if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) {
if (!isVarying(info, v)) {
@ -421,6 +453,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
source.append(" = ");
String namePrefix = getAppendableNameSpace(mapping.getRightVariable());
source.append(namePrefix);
source.append(mapping.getRightVariable().getPrefix());
source.append(mapping.getRightVariable().getName());
if (mapping.getRightSwizzling().length() > 0) {
source.append(".");
@ -602,7 +635,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
* makes sure inPosition attribute is of type vec3 or vec4
* @param var the inPosition attribute
*/
private void fixInPositionType(ShaderNodeVariable var) {
protected void fixInPositionType(ShaderNodeVariable var) {
if(!var.getType().equals("vec3") || !var.getType().equals("vec4")){
var.setType("vec3");
}

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

Loading…
Cancel
Save