Merged master into PBR and migrated to TechniqueDefLogic

define_list_fix
Nehon 9 years ago
commit 0ec2263ae9
  1. 141
      .gitignore
  2. 6
      common.gradle
  3. 6
      jme3-android/src/main/java/com/jme3/app/AndroidHarness.java
  4. 4
      jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java
  5. 21
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  6. 37
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  7. 2
      jme3-bullet-native/src/native/cpp/jmeClasses.cpp
  8. 1
      jme3-bullet-native/src/native/cpp/jmeClasses.h
  9. 24
      jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp
  10. 15
      jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java
  11. 5
      jme3-core/src/main/java/com/jme3/animation/EffectTrack.java
  12. 21
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  13. 591
      jme3-core/src/main/java/com/jme3/app/Application.java
  14. 793
      jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
  15. 12
      jme3-core/src/main/java/com/jme3/app/SimpleApplication.java
  16. 2
      jme3-core/src/main/java/com/jme3/app/StatsAppState.java
  17. 9
      jme3-core/src/main/java/com/jme3/asset/AssetManager.java
  18. 33
      jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
  19. 25
      jme3-core/src/main/java/com/jme3/audio/AudioNode.java
  20. 22
      jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
  21. 45
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  22. 31
      jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java
  23. 21
      jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java
  24. 2
      jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
  25. 3
      jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java
  26. 13
      jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java
  27. 23
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java
  28. 23
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java
  29. 22
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java
  30. 3
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java
  31. 22
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java
  32. 22
      jme3-core/src/main/java/com/jme3/font/BitmapText.java
  33. 7
      jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java
  34. 16
      jme3-core/src/main/java/com/jme3/light/Light.java
  35. 4
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  36. 151
      jme3-core/src/main/java/com/jme3/material/MatParamOverride.java
  37. 6
      jme3-core/src/main/java/com/jme3/material/MatParamTexture.java
  38. 556
      jme3-core/src/main/java/com/jme3/material/Material.java
  39. 3
      jme3-core/src/main/java/com/jme3/material/RenderState.java
  40. 239
      jme3-core/src/main/java/com/jme3/material/Technique.java
  41. 289
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  42. 97
      jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
  43. 178
      jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java
  44. 255
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  45. 218
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java
  46. 157
      jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java
  47. 97
      jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
  48. 29
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  49. 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
  50. 6
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
  51. 52
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  52. 10
      jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java
  53. 3
      jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java
  54. 2
      jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
  55. 2
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  56. 2
      jme3-core/src/main/java/com/jme3/scene/CameraNode.java
  57. 49
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  58. 2
      jme3-core/src/main/java/com/jme3/scene/LightNode.java
  59. 9
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  60. 56
      jme3-core/src/main/java/com/jme3/scene/Node.java
  61. 224
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  62. 1
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  63. 2
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
  64. 2
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  65. 301
      jme3-core/src/main/java/com/jme3/shader/DefineList.java
  66. 7
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
  67. 79
      jme3-core/src/main/java/com/jme3/shader/Shader.java
  68. 47
      jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java
  69. 201
      jme3-core/src/main/java/com/jme3/shader/ShaderKey.java
  70. 113
      jme3-core/src/main/java/com/jme3/shader/Uniform.java
  71. 5
      jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java
  72. 2
      jme3-core/src/main/java/com/jme3/shader/VarType.java
  73. 7
      jme3-core/src/main/java/com/jme3/system/NullContext.java
  74. 2
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  75. 56
      jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java
  76. 20
      jme3-core/src/main/java/com/jme3/util/SafeArrayList.java
  77. 79
      jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
  78. 15
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  79. 2
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  80. 1
      jme3-core/src/main/resources/com/jme3/system/.gitignore
  81. 12
      jme3-core/src/main/resources/com/jme3/system/version.properties
  82. 67
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  83. 10
      jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java
  84. 52
      jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java
  85. 538
      jme3-core/src/test/java/com/jme3/material/MaterialMatParamOverrideTest.java
  86. 38
      jme3-core/src/test/java/com/jme3/math/FastMathTest.java
  87. 342
      jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java
  88. 173
      jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java
  89. 278
      jme3-core/src/test/java/com/jme3/scene/SceneMatParamOverrideTest.java
  90. 300
      jme3-core/src/test/java/com/jme3/shader/DefineListTest.java
  91. 78
      jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java
  92. 55
      jme3-core/src/test/java/com/jme3/system/TestUtil.java
  93. 24
      jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java
  94. 8
      jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java
  95. 42
      jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java
  96. 5
      jme3-examples/build.gradle
  97. 10
      jme3-examples/src/main/java/jme3test/app/TestApplication.java
  98. 4
      jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java
  99. 213
      jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java
  100. 4
      jme3-examples/src/main/java/jme3test/app/TestContextRestart.java
  101. Some files were not shown because too many files have changed in this diff Show More

141
.gitignore vendored

@ -1,34 +1,22 @@
**/nbproject/private/
/.gradle/
/.nb-gradle/private/
/.nb-gradle/profiles/private/
/.nb-gradle/
/.idea/
/dist/
/build/
/bin/
/netbeans/
/sdk/jdks/local/
/jme3-core/build/
/.classpath
/.project
/.settings
*.dll
*.so
*.jnilib
*.dylib
*.iml
.DS_Store
/jme3-core/src/main/resources/com/jme3/system/version.properties
/jme3-plugins/build/
/jme3-desktop/build/
/jme3-android-native/build/
/jme3-android/build/
/jme3-android-examples/build/
/jme3-blender/build/
/jme3-effects/build/
/jme3-bullet/build/
/jme3-terrain/build/
/jme3-bullet-native/build/
/jme3-bullet-native-android/build/
/jme3-jogg/build/
/jme3-jbullet/build/
/jme3-lwjgl/build/
/jme3-networking/build/
/jme3-niftygui/build/
/jme3-testdata/build/
/jme3-examples/build/
/jme3-jogl/build/
/jme3-ios/build/
/jme3-gl-autogen/build/
/jme3-*/build/
/jme3-bullet-native/bullet.zip
/jme3-bullet-native/bullet-2.82-r2704/
/jme3-android-native/openal-soft/
@ -38,112 +26,9 @@
/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.h
/jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.h
/jme3-android-native/stb_image.h
/sdk/jme3-tests-template/src/com/jme3/gde/templates/tests/JmeTestsProject.zip
/sdk/jme3-tests-template/src/com/jme3/gde/templates/tests/JME3TestsAndroidProject.zip
/sdk/jme3-project-testdata/release/
/sdk/JME3TestsTemplateAndroid/src/jme3test/
/sdk/JME3TestsTemplate/src/jme3test/
/sdk/build/
/sdk/jme3-core-baselibs/release/
/sdk/jme3-core-libraries/release/
/sdk/jme3-project-baselibs/release/
/sdk/jme3-project-libraries/release/
/sdk/jme3-codepalette/build/
/sdk/jme3-core-libraries/build/
/sdk/jme3-code-check/build/
/sdk/jme3-core-baselibs/build/
/sdk/jme3-documentation/build/
/sdk/jme3-core-updatecenters/build/
/sdk/jme3-project-testdata/build/
/sdk/jme3-project-libraries/build/
/sdk/jme3-project-baselibs/build/
/sdk/jme3-templates/build/
/sdk/jme3-texture-editor/build/
/sdk/jme3-tests-template/build/
/sdk/jme3-upgrader/build/
/sdk/jme3-core/build/
/sdk/jme3-obfuscate/build/
/sdk/jme3-gui/build/
/sdk/jme3-cinematics/build/
/sdk/jme3-terrain-editor/build/
/sdk/jme3-lwjgl-applet/build/
/sdk/jme3-blender/build/
/sdk/jme3-navmesh-gen/build/
/sdk/jme3-angelfont/build/
/sdk/jme3-materialeditor/build/
/sdk/jme3-android/build/
/sdk/jme3-desktop-executables/build/
/sdk/jme3-ogrexml/build/
/sdk/jme3-ogretools/build/
/sdk/jme3-scenecomposer/build/
/sdk/jme3-assetpack-support/build/
/sdk/jme3-model-importer/build/
/sdk/jme3-wavefront/build/
/sdk/jme3-vehicle-creator/build/
/sdk/jme3-welcome-screen/build/
/sdk/jme3-glsl-support/build/
/sdk/jme3-dark-laf/build/
/sdk/nbproject/private/
/sdk/jme3-scenecomposer/nbproject/private/
/sdk/jme3-core/nbproject/private/
/sdk/jme3-core-baselibs/nbproject/private/
/sdk/jme3-welcome-screen/nbproject/private/
/sdk/jme3-lwjgl-applet/nbproject/private/
/sdk/jme3-ogrexml/nbproject/private/
/sdk/jme3-upgrader/nbproject/private/
/sdk/jme3-obfuscate/nbproject/private/
/sdk/jme3-navmesh-gen/nbproject/private/
/sdk/jme3-wavefront/nbproject/private/
/sdk/jme3-project-libraries/nbproject/private/
/sdk/jme3-ogretools/nbproject/private/
/sdk/jme3-assetpack-support/nbproject/private/
/sdk/jme3-cinematics/nbproject/private/
/sdk/jme3-model-importer/nbproject/private/
/sdk/jme3-desktop-executables/nbproject/private/
/sdk/jme3-glsl-support/nbproject/private/
/sdk/jme3-android/nbproject/private/
/sdk/jme3-angelfont/nbproject/private/
/sdk/jme3-codepalette/nbproject/private/
/sdk/jme3-documentation/nbproject/private/
/sdk/jme3-vehicle-creator/nbproject/private/
/sdk/jme3-code-check/nbproject/private/
/sdk/jme3-blender/nbproject/private/
/sdk/jme3-core-libraries/nbproject/private/
/sdk/jme3-core-updatecenters/nbproject/private/
/sdk/jme3-gui/nbproject/private/
/sdk/jme3-materialeditor/nbproject/private/
/sdk/jme3-project-baselibs/nbproject/private/
/sdk/jme3-project-testdata/nbproject/private/
/sdk/jme3-templates/nbproject/private/
/sdk/jme3-terrain-editor/nbproject/private/
/sdk/jme3-tests-template/nbproject/private/
/sdk/jme3-texture-editor/nbproject/private/
/sdk/JME3TestsTemplate/nbproject/private/
/sdk/JME3TestsTemplateAndroid/nbproject/private/
/bin
/.classpath
/.project
/.settings
*.dll
*.so
*.jnilib
*.dylib
*.iml
/sdk/dist/
!/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll
!/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll
!/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib
!/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib
!/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so
!/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so
/.nb-gradle/
/sdk/ant-jme/nbproject/private/
/sdk/nbi/stub/ext/engine/nbproject/private/
/sdk/nbi/stub/ext/components/products/jdk/nbproject/private/
/sdk/nbi/stub/ext/components/products/blender/nbproject/private/
/sdk/nbi/stub/ext/components/products/helloworld/nbproject/private/
/sdk/BasicGameTemplate/nbproject/private/
/sdk/nbi/stub/ext/components/products/jdk/build/
/sdk/nbi/stub/ext/components/products/jdk/dist/
/sdk/jme3-dark-laf/nbproject/private/
jme3-lwjgl3/build/

@ -51,6 +51,12 @@ javadoc {
}
}
test {
testLogging {
exceptionFormat = 'full'
}
}
task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') {
classifier = 'sources'
from sourceSets*.allSource

@ -50,7 +50,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
/**
* The jme3 application object
*/
protected Application app = null;
protected LegacyApplication app = null;
/**
* Sets the desired RGB size for the surfaceview. 16 = RGB565, 24 = RGB888.
@ -178,7 +178,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
private boolean inConfigChange = false;
private class DataObject {
protected Application app = null;
protected LegacyApplication app = null;
}
@Override
@ -241,7 +241,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
try {
if (app == null) {
@SuppressWarnings("unchecked")
Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
app = clazz.newInstance();
}

@ -207,7 +207,7 @@ public class AndroidHarnessFragment extends Fragment implements
protected ImageView splashImageView = null;
final private String ESCAPE_EVENT = "TouchEscape";
private boolean firstDrawFrame = true;
private Application app = null;
private LegacyApplication app = null;
private int viewWidth = 0;
private int viewHeight = 0;
@ -258,7 +258,7 @@ public class AndroidHarnessFragment extends Fragment implements
try {
if (app == null) {
@SuppressWarnings("unchecked")
Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
app = clazz.newInstance();
}

@ -1,6 +1,7 @@
package com.jme3.scene.plugins.blender.materials;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -157,14 +158,14 @@ public final class MaterialContext implements Savable {
}
// applying textures
int textureIndex = 0;
if (loadedTextures != null && loadedTextures.size() > 0) {
int textureIndex = 0;
if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) {
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length);
}
for (CombinedTexture combinedTexture : loadedTextures) {
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture());
List<Vector2f> uvs = combinedTexture.getResultUVS();
@ -173,13 +174,19 @@ public final class MaterialContext implements Savable {
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);
}
} else {
LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
}
}
} else if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
LOGGER.fine("No textures found for the mesh, but UV coordinates are applied.");
int textureIndex = 0;
}
if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
LOGGER.fine("Storing unused, user defined UV coordinates sets.");
if (userDefinedUVCoordinates.size() > TextureHelper.TEXCOORD_TYPES.length) {
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length);
}
@ -190,7 +197,9 @@ public final class MaterialContext implements Savable {
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
} else {
LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
LOGGER.log(Level.WARNING, "The user's UV set named: '{0}' could not be stored because JME only supports up to {1} different UV's.", new Object[] {
entry.getKey(), TextureHelper.TEXCOORD_TYPES.length
});
}
}
}

@ -119,22 +119,24 @@ public class CombinedTexture {
}
}
/**
* This method flattens the texture and creates a single result of Texture2D
* type.
*
* @param geometry
* the geometry the texture is created for
* @param geometriesOMA
* the old memory address of the geometries list that the given
* geometry belongs to (needed for bounding box creation)
* @param userDefinedUVCoordinates
* the UV's defined by user (null or zero length table if none
* were defined)
* @param blenderContext
* the blender context
*/
public void flatten(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
/**
* This method flattens the texture and creates a single result of Texture2D
* type.
*
* @param geometry
* the geometry the texture is created for
* @param geometriesOMA
* the old memory address of the geometries list that the given
* geometry belongs to (needed for bounding box creation)
* @param userDefinedUVCoordinates
* the UV's defined by user (null or zero length table if none
* were defined)
* @param blenderContext
* the blender context
* @return the name of the user UV coordinates used (null if the UV's were
* generated)
*/
public String flatten(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
Mesh mesh = geometry.getMesh();
Texture previousTexture = null;
UVCoordinatesType masterUVCoordinatesType = null;
@ -226,6 +228,7 @@ public class CombinedTexture {
}
resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture();
masterUserUVSetName = null;
}
// setting additional data
@ -234,6 +237,8 @@ public class CombinedTexture {
// otherwise ugly lines appear between the mesh faces
resultTexture.setMagFilter(MagFilter.Nearest);
resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
return masterUserUVSetName;
}
/**

@ -40,6 +40,7 @@ jclass jmeClasses::PhysicsSpace;
jmethodID jmeClasses::PhysicsSpace_preTick;
jmethodID jmeClasses::PhysicsSpace_postTick;
jmethodID jmeClasses::PhysicsSpace_addCollisionEvent;
jmethodID jmeClasses::PhysicsSpace_notifyCollisionGroupListeners;
jclass jmeClasses::PhysicsGhostObject;
jmethodID jmeClasses::PhysicsGhostObject_addOverlappingObject;
@ -137,6 +138,7 @@ void jmeClasses::initJavaClasses(JNIEnv* env) {
PhysicsSpace_preTick = env->GetMethodID(PhysicsSpace, "preTick_native", "(F)V");
PhysicsSpace_postTick = env->GetMethodID(PhysicsSpace, "postTick_native", "(F)V");
PhysicsSpace_addCollisionEvent = env->GetMethodID(PhysicsSpace, "addCollisionEvent_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;J)V");
PhysicsSpace_notifyCollisionGroupListeners = env->GetMethodID(PhysicsSpace, "notifyCollisionGroupListeners_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;)Z");
if (env->ExceptionCheck()) {
env->Throw(env->ExceptionOccurred());
return;

@ -46,6 +46,7 @@ public:
static jmethodID PhysicsSpace_addCollisionEvent;
static jclass PhysicsGhostObject;
static jmethodID PhysicsGhostObject_addOverlappingObject;
static jmethodID PhysicsSpace_notifyCollisionGroupListeners;
static jclass Vector3f;
static jmethodID Vector3f_set;

@ -187,8 +187,28 @@ void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ,
jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer();
jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer();
if (up0 != NULL && up1 != NULL) {
collides = (up0->group & up1->groups) != 0;
collides = collides && (up1->group & up0->groups);
collides = (up0->group & up1->groups) != 0 || (up1->group & up0->groups) != 0;
if(collides){
jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space;
JNIEnv* env = dynamicsWorld->getEnv();
jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace());
jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject);
jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject);
jboolean notifyResult = env->CallBooleanMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_notifyCollisionGroupListeners, javaCollisionObject0, javaCollisionObject1);
env->DeleteLocalRef(javaPhysicsSpace);
env->DeleteLocalRef(javaCollisionObject0);
env->DeleteLocalRef(javaCollisionObject1);
if (env->ExceptionCheck()) {
env->Throw(env->ExceptionOccurred());
return collides;
}
collides = (bool) notifyResult;
}
//add some additional logic here that modified 'collides'
return collides;

@ -336,6 +336,21 @@ public class PhysicsSpace {
collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, manifoldPointObjectId));
}
private boolean notifyCollisionGroupListeners_native(PhysicsCollisionObject node, PhysicsCollisionObject node1){
PhysicsCollisionGroupListener listener = collisionGroupListeners.get(node.getCollisionGroup());
PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(node1.getCollisionGroup());
boolean result = true;
if(listener != null){
result = listener.collide(node, node1);
}
if(listener1 != null && node.getCollisionGroup() != node1.getCollisionGroup()){
result = listener1.collide(node, node1) && result;
}
return result;
}
/**
* updates the physics space
*

@ -129,11 +129,6 @@ public class EffectTrack implements ClonableTrack {
return c;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}

@ -204,6 +204,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* @param skeleton the skeleton
*/
public SkeletonControl(Skeleton skeleton) {
if (skeleton == null) {
throw new IllegalArgumentException("skeleton cannot be null");
}
this.skeleton = skeleton;
}
@ -406,7 +409,23 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
// Not automatic set cloning yet
Set<Material> newMaterials = new HashSet<Material>();
for( Material m : this.materials ) {
newMaterials.add(cloner.clone(m));
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;
}

@ -33,85 +33,30 @@ package com.jme3.app;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioContext;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.Listener;
import com.jme3.input.*;
import com.jme3.math.Vector3f;
import com.jme3.input.InputManager;
import com.jme3.profile.AppProfiler;
import com.jme3.profile.AppStep;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.system.*;
import com.jme3.system.JmeContext.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The <code>Application</code> class represents an instance of a
* real-time 3D rendering jME application.
*
* An <code>Application</code> provides all the tools that are commonly used in jME3
* applications.
*
* jME3 applications *SHOULD NOT EXTEND* this class but extend {@link com.jme3.app.SimpleApplication} instead.
*
* The <code>Application</code> interface represents the minimum exposed
* capabilities of a concrete jME3 application.
*/
public class Application implements SystemListener {
private static final Logger logger = Logger.getLogger(Application.class.getName());
protected AssetManager assetManager;
protected AudioRenderer audioRenderer;
protected Renderer renderer;
protected RenderManager renderManager;
protected ViewPort viewPort;
protected ViewPort guiViewPort;
protected JmeContext context;
protected AppSettings settings;
protected Timer timer = new NanoTimer();
protected Camera cam;
protected Listener listener;
protected boolean inputEnabled = true;
protected LostFocusBehavior lostFocusBehavior = LostFocusBehavior.ThrottleOnLostFocus;
protected float speed = 1f;
protected boolean paused = false;
protected MouseInput mouseInput;
protected KeyInput keyInput;
protected JoyInput joyInput;
protected TouchInput touchInput;
protected InputManager inputManager;
protected AppStateManager stateManager;
protected AppProfiler prof;
private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
/**
* Create a new instance of <code>Application</code>.
*/
public Application(){
initStateManager();
}
public interface Application {
/**
* Determine the application's behavior when unfocused.
*
* @return The lost focus behavior of the application.
*/
public LostFocusBehavior getLostFocusBehavior() {
return lostFocusBehavior;
}
public LostFocusBehavior getLostFocusBehavior();
/**
* Change the application's behavior when unfocused.
@ -125,9 +70,7 @@ public class Application implements SystemListener {
*
* @see LostFocusBehavior
*/
public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) {
this.lostFocusBehavior = lostFocusBehavior;
}
public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior);
/**
* Returns true if pause on lost focus is enabled, false otherwise.
@ -136,9 +79,7 @@ public class Application implements SystemListener {
*
* @see #getLostFocusBehavior()
*/
public boolean isPauseOnLostFocus() {
return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus;
}
public boolean isPauseOnLostFocus();
/**
* Enable or disable pause on lost focus.
@ -156,49 +97,7 @@ public class Application implements SystemListener {
*
* @see #setLostFocusBehavior(com.jme3.app.LostFocusBehavior)
*/
public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
if (pauseOnLostFocus) {
setLostFocusBehavior(LostFocusBehavior.PauseOnLostFocus);
} else {
setLostFocusBehavior(LostFocusBehavior.Disabled);
}
}
@Deprecated
public void setAssetManager(AssetManager assetManager){
if (this.assetManager != null)
throw new IllegalStateException("Can only set asset manager"
+ " before initialization.");
this.assetManager = assetManager;
}
private void initAssetManager(){
URL assetCfgUrl = null;
if (settings != null){
String assetCfg = settings.getString("AssetConfigURL");
if (assetCfg != null){
try {
assetCfgUrl = new URL(assetCfg);
} catch (MalformedURLException ex) {
}
if (assetCfgUrl == null) {
assetCfgUrl = Application.class.getClassLoader().getResource(assetCfg);
if (assetCfgUrl == null) {
logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
return;
}
}
}
}
if (assetCfgUrl == null) {
assetCfgUrl = JmeSystem.getPlatformAssetConfigURL();
}
if (assetManager == null){
assetManager = JmeSystem.newAssetManager(assetCfgUrl);
}
}
public void setPauseOnLostFocus(boolean pauseOnLostFocus);
/**
* Set the display settings to define the display created.
@ -210,321 +109,83 @@ public class Application implements SystemListener {
*
* @param settings The settings to set.
*/
public void setSettings(AppSettings settings){
this.settings = settings;
if (context != null && settings.useInput() != inputEnabled){
// may need to create or destroy input based
// on settings change
inputEnabled = !inputEnabled;
if (inputEnabled){
initInput();
}else{
destroyInput();
}
}else{
inputEnabled = settings.useInput();
}
}
public void setSettings(AppSettings settings);
/**
* Sets the Timer implementation that will be used for calculating
* frame times. By default, Application will use the Timer as returned
* by the current JmeContext implementation.
*/
public void setTimer(Timer timer){
this.timer = timer;
if (timer != null) {
timer.reset();
}
if (renderManager != null) {
renderManager.setTimer(timer);
}
}
public Timer getTimer(){
return timer;
}
private void initDisplay(){
// aquire important objects
// from the context
settings = context.getSettings();
// Only reset the timer if a user has not already provided one
if (timer == null) {
timer = context.getTimer();
}
renderer = context.getRenderer();
}
private void initAudio(){
if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
audioRenderer = JmeSystem.newAudioRenderer(settings);
audioRenderer.initialize();
AudioContext.setAudioRenderer(audioRenderer);
listener = new Listener();
audioRenderer.setListener(listener);
}
}
/**
* Creates the camera to use for rendering. Default values are perspective
* projection with 45° field of view, with near and far values 1 and 1000
* units respectively.
*/
private void initCamera(){
cam = new Camera(settings.getWidth(), settings.getHeight());
cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
cam.setLocation(new Vector3f(0f, 0f, 10f));
cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
renderManager = new RenderManager(renderer);
//Remy - 09/14/2010 setted the timer in the renderManager
renderManager.setTimer(timer);
if (prof != null) {
renderManager.setAppProfiler(prof);
}
viewPort = renderManager.createMainView("Default", cam);
viewPort.setClearFlags(true, true, true);
// Create a new cam for the gui
Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
guiViewPort = renderManager.createPostView("Gui Default", guiCam);
guiViewPort.setClearFlags(false, false, false);
}
/**
* Initializes mouse and keyboard input. Also
* initializes joystick input if joysticks are enabled in the
* AppSettings.
*/
private void initInput(){
mouseInput = context.getMouseInput();
if (mouseInput != null)
mouseInput.initialize();
keyInput = context.getKeyInput();
if (keyInput != null)
keyInput.initialize();
touchInput = context.getTouchInput();
if (touchInput != null)
touchInput.initialize();
public void setTimer(Timer timer);
if (!settings.getBoolean("DisableJoysticks")){
joyInput = context.getJoyInput();
if (joyInput != null)
joyInput.initialize();
}
inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
}
private void initStateManager(){
stateManager = new AppStateManager(this);
// Always register a ResetStatsState to make sure
// that the stats are cleared every frame
stateManager.attach(new ResetStatsState());
}
public Timer getTimer();
/**
* @return The {@link AssetManager asset manager} for this application.
*/
public AssetManager getAssetManager(){
return assetManager;
}
public AssetManager getAssetManager();
/**
* @return the {@link InputManager input manager}.
*/
public InputManager getInputManager(){
return inputManager;
}
public InputManager getInputManager();
/**
* @return the {@link AppStateManager app state manager}
*/
public AppStateManager getStateManager() {
return stateManager;
}
public AppStateManager getStateManager();
/**
* @return the {@link RenderManager render manager}
*/
public RenderManager getRenderManager() {
return renderManager;
}
public RenderManager getRenderManager();
/**
* @return The {@link Renderer renderer} for the application
*/
public Renderer getRenderer(){
return renderer;
}
public Renderer getRenderer();
/**
* @return The {@link AudioRenderer audio renderer} for the application
*/
public AudioRenderer getAudioRenderer() {
return audioRenderer;
}
public AudioRenderer getAudioRenderer();
/**
* @return The {@link Listener listener} object for audio
*/
public Listener getListener() {
return listener;
}
public Listener getListener();
/**
* @return The {@link JmeContext display context} for the application
*/
public JmeContext getContext(){
return context;
}
/**
* @return The {@link Camera camera} for the application
*/
public Camera getCamera(){
return cam;
}
/**
* Starts the application in {@link Type#Display display} mode.
*
* @see #start(com.jme3.system.JmeContext.Type)
*/
public void start(){
start(JmeContext.Type.Display, false);
}
public JmeContext getContext();
/**
* Starts the application in {@link Type#Display display} mode.
*
* @see #start(com.jme3.system.JmeContext.Type)
* @return The main {@link Camera camera} for the application
*/
public void start(boolean waitFor){
start(JmeContext.Type.Display, waitFor);
}
public Camera getCamera();
/**
* Starts the application.
* Creating a rendering context and executing
* the main loop in a separate thread.
*/
public void start(JmeContext.Type contextType) {
start(contextType, false);
}
public void start();
/**
* Starts the application.
* Creating a rendering context and executing
* the main loop in a separate thread.
*/
public void start(JmeContext.Type contextType, boolean waitFor){
if (context != null && context.isCreated()){
logger.warning("start() called when application already created!");
return;
}
if (settings == null){
settings = new AppSettings(true);
}
logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
context = JmeSystem.newContext(settings, contextType);
context.setSystemListener(this);
context.create(waitFor);
}
public void start(boolean waitFor);
/**
* Sets an AppProfiler hook that will be called back for
* specific steps within a single update frame. Value defaults
* to null.
*/
public void setAppProfiler(AppProfiler prof) {
this.prof = prof;
if (renderManager != null) {
renderManager.setAppProfiler(prof);
}
}
public void setAppProfiler(AppProfiler prof);
/**
* Returns the current AppProfiler hook, or null if none is set.
*/
public AppProfiler getAppProfiler() {
return prof;
}
/**
* Initializes the application's canvas for use.
* <p>
* After calling this method, cast the {@link #getContext() context} to
* {@link JmeCanvasContext},
* then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
* and attach it to an AWT/Swing Frame.
* The rendering thread will start when the canvas becomes visible on
* screen, however if you wish to start the context immediately you
* may call {@link #startCanvas() } to force the rendering thread
* to start.
*
* @see JmeCanvasContext
* @see Type#Canvas
*/
public void createCanvas(){
if (context != null && context.isCreated()){
logger.warning("createCanvas() called when application already created!");
return;
}
if (settings == null){
settings = new AppSettings(true);
}
logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
context.setSystemListener(this);
}
/**
* Starts the rendering thread after createCanvas() has been called.
* <p>
* Same as calling startCanvas(false)
*
* @see #startCanvas(boolean)
*/
public void startCanvas(){
startCanvas(false);
}
/**
* Starts the rendering thread after createCanvas() has been called.
* <p>
* Calling this method is optional, the canvas will start automatically
* when it becomes visible.
*
* @param waitFor If true, the current thread will block until the
* rendering thread is running
*/
public void startCanvas(boolean waitFor){
context.create(waitFor);
}
/**
* Internal use only.
*/
public void reshape(int w, int h){
renderManager.notifyReshape(w, h);
}
public AppProfiler getAppProfiler();
/**
* Restarts the context, applying any changed settings.
@ -533,10 +194,7 @@ public class Application implements SystemListener {
* applied immediately; calling this method forces the context
* to restart, applying the new settings.
*/
public void restart(){
context.setSettings(settings);
context.restart();
}
public void restart();
/**
* Requests the context to close, shutting down the main loop
@ -546,102 +204,14 @@ public class Application implements SystemListener {
*
* @see #stop(boolean)
*/
public void stop(){
stop(false);
}
public void stop();
/**
* Requests the context to close, shutting down the main loop
* and making necessary cleanup operations.
* After the application has stopped, it cannot be used anymore.
*/
public void stop(boolean waitFor){
logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
context.destroy(waitFor);
}
/**
* Do not call manually.
* Callback from ContextListener.
* <p>
* Initializes the <code>Application</code>, by creating a display and
* default camera. If display settings are not specified, a default
* 640x480 display is created. Default values are used for the camera;
* perspective projection with 45° field of view, with near
* and far values 1 and 1000 units respectively.
*/
public void initialize(){
if (assetManager == null){
initAssetManager();
}
initDisplay();
initCamera();
if (inputEnabled){
initInput();
}
initAudio();
// update timer so that the next delta is not too large
// timer.update();
timer.reset();
// user code here..
}
/**
* Internal use only.
*/
public void handleError(String errMsg, Throwable t){
// Print error to log.
logger.log(Level.SEVERE, errMsg, t);
// Display error message on screen if not in headless mode
if (context.getType() != JmeContext.Type.Headless) {
if (t != null) {
JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() +
(t.getMessage() != null ? ": " + t.getMessage() : ""));
} else {
JmeSystem.showErrorDialog(errMsg);
}
}
stop(); // stop the application
}
/**
* Internal use only.
*/
public void gainFocus(){
if (lostFocusBehavior != LostFocusBehavior.Disabled) {
if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
paused = false;
}
context.setAutoFlushFrames(true);
if (inputManager != null) {
inputManager.reset();
}
}
}
/**
* Internal use only.
*/
public void loseFocus(){
if (lostFocusBehavior != LostFocusBehavior.Disabled){
if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
paused = true;
}
context.setAutoFlushFrames(false);
}
}
/**
* Internal use only.
*/
public void requestClose(boolean esc){
context.destroy(false);
}
public void stop(boolean waitFor);
/**
* Enqueues a task/callable object to execute in the jME3
@ -653,11 +223,7 @@ public class Application implements SystemListener {
*
* @param callable The callable to run in the main jME3 thread
*/
public <V> Future<V> enqueue(Callable<V> callable) {
AppTask<V> task = new AppTask<V>(callable);
taskQueue.add(task);
return task;
}
public <V> Future<V> enqueue(Callable<V> callable);
/**
* Enqueues a runnable object to execute in the jME3
@ -669,106 +235,13 @@ public class Application implements SystemListener {
*
* @param runnable The runnable to run in the main jME3 thread
*/
public void enqueue(Runnable runnable){
enqueue(new RunnableWrapper(runnable));
}
/**
* Runs tasks enqueued via {@link #enqueue(Callable)}
*/
protected void runQueuedTasks() {
AppTask<?> task;
while( (task = taskQueue.poll()) != null ) {
if (!task.isCancelled()) {
task.invoke();
}
}
}
/**
* Do not call manually.
* Callback from ContextListener.
*/
public void update(){
// Make sure the audio renderer is available to callables
AudioContext.setAudioRenderer(audioRenderer);
if (prof!=null) prof.appStep(AppStep.QueuedTasks);
runQueuedTasks();
if (speed == 0 || paused)
return;
timer.update();
if (inputEnabled){
if (prof!=null) prof.appStep(AppStep.ProcessInput);
inputManager.update(timer.getTimePerFrame());
}
if (audioRenderer != null){
if (prof!=null) prof.appStep(AppStep.ProcessAudio);
audioRenderer.update(timer.getTimePerFrame());
}
// user code here..
}
protected void destroyInput(){
if (mouseInput != null)
mouseInput.destroy();
if (keyInput != null)
keyInput.destroy();
if (joyInput != null)
joyInput.destroy();
if (touchInput != null)
touchInput.destroy();
inputManager = null;
}
/**
* Do not call manually.
* Callback from ContextListener.
*/
public void destroy(){
stateManager.cleanup();
destroyInput();
if (audioRenderer != null)
audioRenderer.cleanup();
timer.reset();
}
public void enqueue(Runnable runnable);
/**
* @return The GUI viewport. Which is used for the on screen
* statistics and FPS.
*/
public ViewPort getGuiViewPort() {
return guiViewPort;
}
public ViewPort getViewPort() {
return viewPort;
}
private class RunnableWrapper implements Callable{
private final Runnable runnable;
public RunnableWrapper(Runnable runnable){
this.runnable = runnable;
}
@Override
public Object call(){
runnable.run();
return null;
}
}
public ViewPort getGuiViewPort();
public ViewPort getViewPort();
}

@ -0,0 +1,793 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.app;
import com.jme3.app.state.AppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioContext;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.Listener;
import com.jme3.input.*;
import com.jme3.math.Vector3f;
import com.jme3.profile.AppProfiler;
import com.jme3.profile.AppStep;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.system.*;
import com.jme3.system.JmeContext.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The <code>LegacyApplication</code> class represents an instance of a
* real-time 3D rendering jME application.
*
* An <code>LegacyApplication</code> provides all the tools that are commonly used in jME3
* applications.
*
* jME3 applications *SHOULD NOT EXTEND* this class but extend {@link com.jme3.app.SimpleApplication} instead.
*
*/
public class LegacyApplication implements Application, SystemListener {
private static final Logger logger = Logger.getLogger(LegacyApplication.class.getName());
protected AssetManager assetManager;
protected AudioRenderer audioRenderer;
protected Renderer renderer;
protected RenderManager renderManager;
protected ViewPort viewPort;
protected ViewPort guiViewPort;
protected JmeContext context;
protected AppSettings settings;
protected Timer timer = new NanoTimer();
protected Camera cam;
protected Listener listener;
protected boolean inputEnabled = true;
protected LostFocusBehavior lostFocusBehavior = LostFocusBehavior.ThrottleOnLostFocus;
protected float speed = 1f;
protected boolean paused = false;
protected MouseInput mouseInput;
protected KeyInput keyInput;
protected JoyInput joyInput;
protected TouchInput touchInput;
protected InputManager inputManager;
protected AppStateManager stateManager;
protected AppProfiler prof;
private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
/**
* Create a new instance of <code>LegacyApplication</code>.
*/
public LegacyApplication() {
this((AppState[])null);
}
/**
* Create a new instance of <code>LegacyApplication</code>, preinitialized
* with the specified set of app states.
*/
public LegacyApplication( AppState... initialStates ) {
initStateManager();
if (initialStates != null) {
for (AppState a : initialStates) {
if (a != null) {
stateManager.attach(a);
}
}
}
}
/**
* Determine the application's behavior when unfocused.
*
* @return The lost focus behavior of the application.
*/
public LostFocusBehavior getLostFocusBehavior() {
return lostFocusBehavior;
}
/**
* Change the application's behavior when unfocused.
*
* By default, the application will
* {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop}
* so as to not take 100% CPU usage when it is not in focus, e.g.
* alt-tabbed, minimized, or obstructed by another window.
*
* @param lostFocusBehavior The new lost focus behavior to use.
*
* @see LostFocusBehavior
*/
public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) {
this.lostFocusBehavior = lostFocusBehavior;
}
/**
* Returns true if pause on lost focus is enabled, false otherwise.
*
* @return true if pause on lost focus is enabled
*
* @see #getLostFocusBehavior()
*/
public boolean isPauseOnLostFocus() {
return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus;
}
/**
* Enable or disable pause on lost focus.
* <p>
* By default, pause on lost focus is enabled.
* If enabled, the application will stop updating
* when it loses focus or becomes inactive (e.g. alt-tab).
* For online or real-time applications, this might not be preferable,
* so this feature should be set to disabled. For other applications,
* it is best to keep it on so that CPU usage is not used when
* not necessary.
*
* @param pauseOnLostFocus True to enable pause on lost focus, false
* otherwise.
*
* @see #setLostFocusBehavior(com.jme3.app.LostFocusBehavior)
*/
public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
if (pauseOnLostFocus) {
setLostFocusBehavior(LostFocusBehavior.PauseOnLostFocus);
} else {
setLostFocusBehavior(LostFocusBehavior.Disabled);
}
}
@Deprecated
public void setAssetManager(AssetManager assetManager){
if (this.assetManager != null)
throw new IllegalStateException("Can only set asset manager"
+ " before initialization.");
this.assetManager = assetManager;
}
private void initAssetManager(){
URL assetCfgUrl = null;
if (settings != null){
String assetCfg = settings.getString("AssetConfigURL");
if (assetCfg != null){
try {
assetCfgUrl = new URL(assetCfg);
} catch (MalformedURLException ex) {
}
if (assetCfgUrl == null) {
assetCfgUrl = LegacyApplication.class.getClassLoader().getResource(assetCfg);
if (assetCfgUrl == null) {
logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
return;
}
}
}
}
if (assetCfgUrl == null) {
assetCfgUrl = JmeSystem.getPlatformAssetConfigURL();
}
if (assetManager == null){
assetManager = JmeSystem.newAssetManager(assetCfgUrl);
}
}
/**
* Set the display settings to define the display created.
* <p>
* Examples of display parameters include display pixel width and height,
* color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
* If this method is called while the application is already running, then
* {@link #restart() } must be called to apply the settings to the display.
*
* @param settings The settings to set.
*/
public void setSettings(AppSettings settings){
this.settings = settings;
if (context != null && settings.useInput() != inputEnabled){
// may need to create or destroy input based
// on settings change
inputEnabled = !inputEnabled;
if (inputEnabled){
initInput();
}else{
destroyInput();
}
}else{
inputEnabled = settings.useInput();
}
}
/**
* Sets the Timer implementation that will be used for calculating
* frame times. By default, Application will use the Timer as returned
* by the current JmeContext implementation.
*/
public void setTimer(Timer timer){
this.timer = timer;
if (timer != null) {
timer.reset();
}
if (renderManager != null) {
renderManager.setTimer(timer);
}
}
public Timer getTimer(){
return timer;
}
private void initDisplay(){
// aquire important objects
// from the context
settings = context.getSettings();
// Only reset the timer if a user has not already provided one
if (timer == null) {
timer = context.getTimer();
}
renderer = context.getRenderer();
}
private void initAudio(){
if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
audioRenderer = JmeSystem.newAudioRenderer(settings);
audioRenderer.initialize();
AudioContext.setAudioRenderer(audioRenderer);
listener = new Listener();
audioRenderer.setListener(listener);
}
}
/**
* Creates the camera to use for rendering. Default values are perspective
* projection with 45° field of view, with near and far values 1 and 1000
* units respectively.
*/
private void initCamera(){
cam = new Camera(settings.getWidth(), settings.getHeight());
cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
cam.setLocation(new Vector3f(0f, 0f, 10f));
cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
renderManager = new RenderManager(renderer);
//Remy - 09/14/2010 setted the timer in the renderManager
renderManager.setTimer(timer);
if (prof != null) {
renderManager.setAppProfiler(prof);
}
viewPort = renderManager.createMainView("Default", cam);
viewPort.setClearFlags(true, true, true);
// Create a new cam for the gui
Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
guiViewPort = renderManager.createPostView("Gui Default", guiCam);
guiViewPort.setClearFlags(false, false, false);
}
/**
* Initializes mouse and keyboard input. Also
* initializes joystick input if joysticks are enabled in the
* AppSettings.
*/
private void initInput(){
mouseInput = context.getMouseInput();
if (mouseInput != null)
mouseInput.initialize();
keyInput = context.getKeyInput();
if (keyInput != null)
keyInput.initialize();
touchInput = context.getTouchInput();
if (touchInput != null)
touchInput.initialize();
if (!settings.getBoolean("DisableJoysticks")){
joyInput = context.getJoyInput();
if (joyInput != null)
joyInput.initialize();
}
inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
}
private void initStateManager(){
stateManager = new AppStateManager(this);
// Always register a ResetStatsState to make sure
// that the stats are cleared every frame
stateManager.attach(new ResetStatsState());
}
/**
* @return The {@link AssetManager asset manager} for this application.
*/
public AssetManager getAssetManager(){
return assetManager;
}
/**
* @return the {@link InputManager input manager}.
*/
public InputManager getInputManager(){
return inputManager;
}
/**
* @return the {@link AppStateManager app state manager}
*/
public AppStateManager getStateManager() {
return stateManager;
}
/**
* @return the {@link RenderManager render manager}
*/
public RenderManager getRenderManager() {
return renderManager;
}
/**
* @return The {@link Renderer renderer} for the application
*/
public Renderer getRenderer(){
return renderer;
}
/**
* @return The {@link AudioRenderer audio renderer} for the application
*/
public AudioRenderer getAudioRenderer() {
return audioRenderer;
}
/**
* @return The {@link Listener listener} object for audio
*/
public Listener getListener() {
return listener;
}
/**
* @return The {@link JmeContext display context} for the application
*/
public JmeContext getContext(){
return context;
}
/**
* @return The {@link Camera camera} for the application
*/
public Camera getCamera(){
return cam;
}
/**
* Starts the application in {@link Type#Display display} mode.
*
* @see #start(com.jme3.system.JmeContext.Type)
*/
public void start(){
start(JmeContext.Type.Display, false);
}
/**
* Starts the application in {@link Type#Display display} mode.
*
* @see #start(com.jme3.system.JmeContext.Type)
*/
public void start(boolean waitFor){
start(JmeContext.Type.Display, waitFor);
}
/**
* Starts the application.
* Creating a rendering context and executing
* the main loop in a separate thread.
*/
public void start(JmeContext.Type contextType) {
start(contextType, false);
}
/**
* Starts the application.
* Creating a rendering context and executing
* the main loop in a separate thread.
*/
public void start(JmeContext.Type contextType, boolean waitFor){
if (context != null && context.isCreated()){
logger.warning("start() called when application already created!");
return;
}
if (settings == null){
settings = new AppSettings(true);
}
logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
context = JmeSystem.newContext(settings, contextType);
context.setSystemListener(this);
context.create(waitFor);
}
/**
* Sets an AppProfiler hook that will be called back for
* specific steps within a single update frame. Value defaults
* to null.
*/
public void setAppProfiler(AppProfiler prof) {
this.prof = prof;
if (renderManager != null) {
renderManager.setAppProfiler(prof);
}
}
/**
* Returns the current AppProfiler hook, or null if none is set.
*/
public AppProfiler getAppProfiler() {
return prof;
}
/**
* Initializes the application's canvas for use.
* <p>
* After calling this method, cast the {@link #getContext() context} to
* {@link JmeCanvasContext},
* then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
* and attach it to an AWT/Swing Frame.
* The rendering thread will start when the canvas becomes visible on
* screen, however if you wish to start the context immediately you
* may call {@link #startCanvas() } to force the rendering thread
* to start.
*
* @see JmeCanvasContext
* @see Type#Canvas
*/
public void createCanvas(){
if (context != null && context.isCreated()){
logger.warning("createCanvas() called when application already created!");
return;
}
if (settings == null){
settings = new AppSettings(true);
}
logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
context.setSystemListener(this);
}
/**
* Starts the rendering thread after createCanvas() has been called.
* <p>
* Same as calling startCanvas(false)
*
* @see #startCanvas(boolean)
*/
public void startCanvas(){
startCanvas(false);
}
/**
* Starts the rendering thread after createCanvas() has been called.
* <p>
* Calling this method is optional, the canvas will start automatically
* when it becomes visible.
*
* @param waitFor If true, the current thread will block until the
* rendering thread is running
*/
public void startCanvas(boolean waitFor){
context.create(waitFor);
}
/**
* Internal use only.
*/
public void reshape(int w, int h){
if (renderManager != null) {
renderManager.notifyReshape(w, h);
}
}
/**
* Restarts the context, applying any changed settings.
* <p>
* Changes to the {@link AppSettings} of this Application are not
* applied immediately; calling this method forces the context
* to restart, applying the new settings.
*/
public void restart(){
context.setSettings(settings);
context.restart();
}
/**
* Requests the context to close, shutting down the main loop
* and making necessary cleanup operations.
*
* Same as calling stop(false)
*
* @see #stop(boolean)
*/
public void stop(){
stop(false);
}
/**
* Requests the context to close, shutting down the main loop
* and making necessary cleanup operations.
* After the application has stopped, it cannot be used anymore.
*/
public void stop(boolean waitFor){
logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
context.destroy(waitFor);
}
/**
* Do not call manually.
* Callback from ContextListener.
* <p>
* Initializes the <code>Application</code>, by creating a display and
* default camera. If display settings are not specified, a default
* 640x480 display is created. Default values are used for the camera;
* perspective projection with 45° field of view, with near
* and far values 1 and 1000 units respectively.
*/
public void initialize(){
if (assetManager == null){
initAssetManager();
}
initDisplay();
initCamera();
if (inputEnabled){
initInput();
}
initAudio();
// update timer so that the next delta is not too large
// timer.update();
timer.reset();
// user code here..
}
/**
* Internal use only.
*/
public void handleError(String errMsg, Throwable t){
// Print error to log.
logger.log(Level.SEVERE, errMsg, t);
// Display error message on screen if not in headless mode
if (context.getType() != JmeContext.Type.Headless) {
if (t != null) {
JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() +
(t.getMessage() != null ? ": " + t.getMessage() : ""));
} else {
JmeSystem.showErrorDialog(errMsg);
}
}
stop(); // stop the application
}
/**
* Internal use only.
*/
public void gainFocus(){
if (lostFocusBehavior != LostFocusBehavior.Disabled) {
if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
paused = false;
}
context.setAutoFlushFrames(true);
if (inputManager != null) {
inputManager.reset();
}
}
}
/**
* Internal use only.
*/
public void loseFocus(){
if (lostFocusBehavior != LostFocusBehavior.Disabled){
if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
paused = true;
}
context.setAutoFlushFrames(false);
}
}
/**
* Internal use only.
*/
public void requestClose(boolean esc){
context.destroy(false);
}
/**
* Enqueues a task/callable object to execute in the jME3
* rendering thread.
* <p>
* Callables are executed right at the beginning of the main loop.
* They are executed even if the application is currently paused
* or out of focus.
*
* @param callable The callable to run in the main jME3 thread
*/
public <V> Future<V> enqueue(Callable<V> callable) {
AppTask<V> task = new AppTask<V>(callable);
taskQueue.add(task);
return task;
}
/**
* Enqueues a runnable object to execute in the jME3
* rendering thread.
* <p>
* Runnables are executed right at the beginning of the main loop.
* They are executed even if the application is currently paused
* or out of focus.
*
* @param runnable The runnable to run in the main jME3 thread
*/
public void enqueue(Runnable runnable){
enqueue(new RunnableWrapper(runnable));
}
/**
* Runs tasks enqueued via {@link #enqueue(Callable)}
*/
protected void runQueuedTasks() {
AppTask<?> task;
while( (task = taskQueue.poll()) != null ) {
if (!task.isCancelled()) {
task.invoke();
}
}
}
/**
* Do not call manually.
* Callback from ContextListener.
*/
public void update(){
// Make sure the audio renderer is available to callables
AudioContext.setAudioRenderer(audioRenderer);
if (prof!=null) prof.appStep(AppStep.QueuedTasks);
runQueuedTasks();
if (speed == 0 || paused)
return;
timer.update();
if (inputEnabled){
if (prof!=null) prof.appStep(AppStep.ProcessInput);
inputManager.update(timer.getTimePerFrame());
}
if (audioRenderer != null){
if (prof!=null) prof.appStep(AppStep.ProcessAudio);
audioRenderer.update(timer.getTimePerFrame());
}
// user code here..
}
protected void destroyInput(){
if (mouseInput != null)
mouseInput.destroy();
if (keyInput != null)
keyInput.destroy();
if (joyInput != null)
joyInput.destroy();
if (touchInput != null)
touchInput.destroy();
inputManager = null;
}
/**
* Do not call manually.
* Callback from ContextListener.
*/
public void destroy(){
stateManager.cleanup();
destroyInput();
if (audioRenderer != null)
audioRenderer.cleanup();
timer.reset();
}
/**
* @return The GUI viewport. Which is used for the on screen
* statistics and FPS.
*/
public ViewPort getGuiViewPort() {
return guiViewPort;
}
public ViewPort getViewPort() {
return viewPort;
}
private class RunnableWrapper implements Callable{
private final Runnable runnable;
public RunnableWrapper(Runnable runnable){
this.runnable = runnable;
}
@Override
public Object call(){
runnable.run();
return null;
}
}
}

@ -63,7 +63,7 @@ import com.jme3.system.JmeSystem;
* A {@link com.jme3.app.FlyCamAppState} is by default attached as well and can
* be removed by calling <code>stateManager.detach( stateManager.getState(FlyCamAppState.class) );</code>
*/
public abstract class SimpleApplication extends Application {
public abstract class SimpleApplication extends LegacyApplication {
public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
@ -100,15 +100,7 @@ public abstract class SimpleApplication extends Application {
}
public SimpleApplication( AppState... initialStates ) {
super();
if (initialStates != null) {
for (AppState a : initialStates) {
if (a != null) {
stateManager.attach(a);
}
}
}
super(initialStates);
}
@Override

@ -195,7 +195,7 @@ public class StatsAppState extends AbstractAppState {
}
public void loadDarken() {
Material mat = new Material(app.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new ColorRGBA(0,0,0,0.5f));
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

@ -41,9 +41,7 @@ import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Caps;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.OBJLoader;
import com.jme3.shader.Shader;
import com.jme3.shader.ShaderGenerator;
import com.jme3.shader.ShaderKey;
import com.jme3.texture.Texture;
import com.jme3.texture.plugins.TGALoader;
import java.io.IOException;
@ -320,13 +318,6 @@ public interface AssetManager {
*/
public Material loadMaterial(String name);
/**
* Loads shader file(s), shouldn't be used by end-user in most cases.
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
*/
public Shader loadShader(ShaderKey key);
/**
* Load a font file. Font files are in AngelCode text format,
* and are with the extension "fnt".

@ -32,7 +32,6 @@
package com.jme3.asset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.SimpleAssetCache;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioKey;
import com.jme3.font.BitmapFont;
@ -42,9 +41,7 @@ import com.jme3.renderer.Caps;
import com.jme3.scene.Spatial;
import com.jme3.shader.Glsl100ShaderGenerator;
import com.jme3.shader.Glsl150ShaderGenerator;
import com.jme3.shader.Shader;
import com.jme3.shader.ShaderGenerator;
import com.jme3.shader.ShaderKey;
import com.jme3.system.JmeSystem;
import com.jme3.texture.Texture;
import java.io.IOException;
@ -431,36 +428,6 @@ public class DesktopAssetManager implements AssetManager {
return loadFilter(new FilterKey(name));
}
/**
* Load a vertex/fragment shader combo.
*
* @param key
* @return the loaded {@link Shader}
*/
public Shader loadShader(ShaderKey key){
// cache abuse in method
// that doesn't use loaders/locators
AssetCache cache = handler.getCache(SimpleAssetCache.class);
Shader shader = (Shader) cache.getFromCache(key);
if (shader == null){
if (key.isUsesShaderNodes()) {
if(shaderGenerator == null){
throw new UnsupportedOperationException("ShaderGenerator was not initialized, make sure assetManager.getGenerator(caps) has been called");
}
shader = shaderGenerator.generateShader();
} else {
shader = new Shader();
shader.initialize();
for (Shader.ShaderType shaderType : key.getUsedShaderPrograms()) {
shader.addSource(shaderType,key.getShaderProgramName(shaderType),(String) loadAsset(new AssetKey(key.getShaderProgramName(shaderType))),key.getDefines().getCompiled(),key.getShaderProgramLanguage(shaderType));
}
}
cache.addToCache(key, shader);
}
return shader;
}
/**
* {@inheritDoc}
*/

@ -41,6 +41,7 @@ import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.util.PlaceholderAssets;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -724,6 +725,30 @@ public class AudioNode extends Node implements AudioSource {
return clone;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.direction = cloner.clone(direction);
this.velocity = cloner.clone(velocity);
// Change in behavior: the filters were not cloned before meaning
// that two cloned audio nodes would share the same filter instance.
// While settings will only be applied when the filter is actually
// set, I think it's probably surprising to callers if the values of
// a filter change from one AudioNode when a different AudioNode's
// filter attributes are updated.
// Plus if they disable and re-enable the thing using the filter then
// the settings get reapplied and it might be surprising to have them
// suddenly be strange.
// ...so I'll clone them. -pspeed
this.dryFilter = cloner.clone(dryFilter);
this.reverbFilter = cloner.clone(reverbFilter);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2016 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -64,9 +64,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
protected int currentWayPoint;
protected float currentValue;
protected Vector3f direction = new Vector3f();
protected Vector3f lookAt;
protected Vector3f lookAt = null;
protected Vector3f upVector = Vector3f.UNIT_Y;
protected Quaternion rotation;
protected Quaternion rotation = null;
protected Direction directionType = Direction.None;
protected MotionPath path;
private boolean isControl = true;
@ -209,9 +209,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(lookAt, "lookAt", Vector3f.ZERO);
oc.write(lookAt, "lookAt", null);
oc.write(upVector, "upVector", Vector3f.UNIT_Y);
oc.write(rotation, "rotation", Quaternion.IDENTITY);
oc.write(rotation, "rotation", null);
oc.write(directionType, "directionType", Direction.None);
oc.write(path, "path", null);
}
@ -220,9 +220,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
lookAt = (Vector3f) in.readSavable("lookAt", Vector3f.ZERO);
lookAt = (Vector3f) in.readSavable("lookAt", null);
upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y);
rotation = (Quaternion) in.readSavable("rotation", Quaternion.IDENTITY);
rotation = (Quaternion) in.readSavable("rotation", null);
directionType = in.readEnum("directionType", Direction.class, Direction.None);
path = (MotionPath) in.readSavable("path", null);
}
@ -280,9 +280,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
@ -299,9 +299,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;

@ -174,6 +174,13 @@ public class ParticleEmitter extends Geometry {
@Override
public ParticleEmitter clone(boolean cloneMaterial) {
return (ParticleEmitter)super.clone(cloneMaterial);
}
/**
* The old clone() method that did not use the new Cloner utility.
*/
public ParticleEmitter oldClone(boolean cloneMaterial) {
ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial);
clone.shape = shape.deepClone();
@ -211,6 +218,44 @@ public class ParticleEmitter extends Geometry {
return clone;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.shape = cloner.clone(shape);
this.control = cloner.clone(control);
this.faceNormal = cloner.clone(faceNormal);
this.startColor = cloner.clone(startColor);
this.endColor = cloner.clone(endColor);
this.particleInfluencer = cloner.clone(particleInfluencer);
// change in behavior: gravity was not cloned before -pspeed
this.gravity = cloner.clone(gravity);
// So, simply setting the mesh type will cause all kinds of things
// to happen:
// 1) the new mesh gets created.
// 2) it is set to the Geometry
// 3) the particles array is recreated because setNumParticles()
//
// ...so this should be equivalent but simpler than half of the old clone()
// method. Note: we do not ever want to share particleMesh so we do not
// clone it at all.
setMeshType(meshType);
// change in behavior: temp and lastPos were not cloned before...
// perhaps because it was believed that 'transient' fields were exluded
// from cloning? (they aren't)
// If it was ok for these to be shared because of how they are used
// then they could just as well be made static... else I think it's clearer
// to clone them.
this.temp = cloner.clone(temp);
this.lastPos = cloner.clone(lastPos);
}
public ParticleEmitter(String name, Type type, int numParticles) {
super(name);
setBatchHint(BatchHint.Never);

@ -39,6 +39,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -109,6 +111,35 @@ public class DefaultParticleInfluencer implements ParticleInfluencer {
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.initialVelocity = cloner.clone(initialVelocity);
// Change in behavior: I'm cloning 'for real' the 'temp' field because
// otherwise it will be shared across all clones. Note: if this is
// ok because of how its used then it might as well be static and let
// everything share it.
// Note 2: transient fields _are_ cloned just like anything else so
// thinking it wouldn't get cloned is also not right.
// -pspeed
this.temp = cloner.clone(temp);
}
@Override
public void setInitialVelocity(Vector3f initialVelocity) {
this.initialVelocity.set(initialVelocity);

@ -36,6 +36,8 @@ import com.jme3.effect.shapes.EmitterShape;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -83,4 +85,23 @@ public class EmptyParticleInfluencer implements ParticleInfluencer {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
}
}

@ -39,6 +39,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**

@ -36,12 +36,13 @@ import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.shapes.EmitterShape;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.JmeCloneable;
/**
* An interface that defines the methods to affect initial velocity of the particles.
* @author Marcin Roguski (Kaelthas)
*/
public interface ParticleInfluencer extends Savable, Cloneable {
public interface ParticleInfluencer extends Savable, Cloneable, JmeCloneable {
/**
* This method influences the particle.

@ -38,6 +38,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
@ -119,6 +120,18 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
this.horizontal = horizontal;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// Change in behavior: the old origin was not cloned -pspeed
this.origin = cloner.clone(origin);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);

@ -37,6 +37,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class EmitterBoxShape implements EmitterShape {
@ -86,6 +88,27 @@ public class EmitterBoxShape implements EmitterShape {
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.min = cloner.clone(min);
this.len = cloner.clone(len);
}
public Vector3f getMin() {
return min;
}

@ -40,6 +40,8 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@ -168,6 +170,27 @@ public class EmitterMeshVertexShape implements EmitterShape {
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.vertices = cloner.clone(vertices);
this.normals = cloner.clone(normals);
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);

@ -35,6 +35,8 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class EmitterPointShape implements EmitterShape {
@ -59,6 +61,26 @@ public class EmitterPointShape implements EmitterShape {
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.point = cloner.clone(point);
}
@Override
public void getRandomPoint(Vector3f store) {
store.set(point);

@ -33,12 +33,13 @@ package com.jme3.effect.shapes;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.JmeCloneable;
/**
* This interface declares methods used by all shapes that represent particle emitters.
* @author Kirill
*/
public interface EmitterShape extends Savable, Cloneable {
public interface EmitterShape extends Savable, Cloneable, JmeCloneable {
/**
* This method fills in the initial position of the particle.

@ -37,6 +37,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class EmitterSphereShape implements EmitterShape {
@ -71,6 +73,26 @@ public class EmitterSphereShape implements EmitterShape {
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.center = cloner.clone(center);
}
@Override
public void getRandomPoint(Vector3f store) {
do {

@ -38,6 +38,7 @@ import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.util.clone.Cloner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -84,6 +85,27 @@ public class BitmapText extends Node {
return clone;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
for( int i = 0; i < textPages.length; i++ ) {
textPages[i] = cloner.clone(textPages[i]);
}
this.block = cloner.clone(block);
// Change in behavior: The 'letters' field was not cloned or recreated
// before. I'm not sure how this worked and suspect BitmapText was just
// not cloneable if you planned to change the text later. -pspeed
this.letters = new Letters(font, block, letters.getQuad().isRightToLeft());
// Just noticed BitmapText is not even writable/readable really...
// so I guess cloning doesn't come up that often.
}
public BitmapFont getFont() {
return font;
}

@ -123,6 +123,13 @@ class BitmapTextPage extends Geometry {
return clone;
}
// Here is where one might add JmeCloneable related stuff except
// the old clone() method doesn't actually bother to clone anything.
// The arrays and the pageQuads are shared across all BitmapTextPage
// clones and it doesn't seem to bother anything. That means the
// fields could probably just as well be static... but this code is
// all very fragile. I'm not tipping that particular boat today. -pspeed
void assemble(Letters quads) {
pageQuads.clear();
quads.rewind();

@ -191,6 +191,22 @@ public abstract class Light implements Savable, Cloneable {
this.enabled = enabled;
}
public boolean isFrustumCheckNeeded() {
return frustumCheckNeeded;
}
public void setFrustumCheckNeeded(boolean frustumCheckNeeded) {
this.frustumCheckNeeded = frustumCheckNeeded;
}
public boolean isIntersectsFrustum() {
return intersectsFrustum;
}
public void setIntersectsFrustum(boolean intersectsFrustum) {
this.intersectsFrustum = intersectsFrustum;
}
/**
* Determines if the light intersects with the given bounding box.
* <p>

@ -34,7 +34,6 @@ package com.jme3.material;
import com.jme3.asset.TextureKey;
import com.jme3.export.*;
import com.jme3.math.*;
import com.jme3.renderer.Renderer;
import com.jme3.shader.VarType;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
@ -129,9 +128,6 @@ public class MatParam implements Savable, Cloneable {
this.value = value;
}
void apply(Renderer r, Technique technique) {
technique.updateUniformParam(getPrefixedName(), getVarType(), getValue());
}
/**
* Returns the material parameter value as it would appear in a J3M

@ -0,0 +1,151 @@
/*
* 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.material;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Spatial;
import com.jme3.shader.VarType;
import java.io.IOException;
/**
* <code>MatParamOverride</code> is a mechanism by which
* {@link MatParam material parameters} can be overridden on the scene graph.
* <p>
* A scene branch which has a <code>MatParamOverride</code> applied to it will
* cause all material parameters with the same name and type to have their value
* replaced with the value set on the <code>MatParamOverride</code>. If those
* parameters are mapped to a define, then the define will be overridden as well
* using the same rules as the ones used for regular material parameters.
* <p>
* <code>MatParamOverrides</code> are applied to a {@link Spatial} via the
* {@link Spatial#addMatParamOverride(com.jme3.material.MatParamOverride)}
* method. They are propagated to child <code>Spatials</code> via
* {@link Spatial#updateGeometricState()} similar to how lights are propagated.
* <p>
* Example:<br>
* <pre>
* {@code
*
* Geometry box = new Geometry("Box", new Box(1,1,1));
* Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
* mat.setColor("Color", ColorRGBA.Blue);
* box.setMaterial(mat);
* rootNode.attachChild(box);
*
* // ... later ...
* MatParamOverride override = new MatParamOverride(Type.Vector4, "Color", ColorRGBA.Red);
* rootNode.addMatParamOverride(override);
*
* // After adding the override to the root node, the box becomes red.
* }
* </pre>
*
* @author Kirill Vainer
* @see Spatial#addMatParamOverride(com.jme3.material.MatParamOverride)
* @see Spatial#getWorldMatParamOverrides()
*/
public final class MatParamOverride extends MatParam {
private boolean enabled = true;
/**
* Serialization only. Do not use.
*/
public MatParamOverride() {
super();
}
/**
* Create a new <code>MatParamOverride</code>.
*
* Overrides are created enabled by default.
*
* @param type The type of parameter.
* @param name The name of the parameter.
* @param value The value to set the material parameter to.
*/
public MatParamOverride(VarType type, String name, Object value) {
super(type, name, value);
}
@Override
public boolean equals(Object obj) {
return super.equals(obj) && this.enabled == ((MatParamOverride) obj).enabled;
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 59 * hash + (enabled ? 1 : 0);
return hash;
}
/**
* Determine if the <code>MatParamOverride</code> is enabled or disabled.
*
* @return true if enabled, false if disabled.
* @see #setEnabled(boolean)
*/
public boolean isEnabled() {
return enabled;
}
/**
* Enable or disable this <code>MatParamOverride</code>.
*
* When disabled, the override will continue to propagate through the scene
* graph like before, but it will have no effect on materials. Overrides are
* enabled by default.
*
* @param enabled Whether to enable or disable this override.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(enabled, "enabled", true);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
enabled = ic.readBoolean("enabled", true);
}
}

@ -100,12 +100,6 @@ public class MatParamTexture extends MatParam {
return unit;
}
@Override
public void apply(Renderer r, Technique technique) {
TechniqueDef techDef = technique.getDef();
r.setTexture(getUnit(), getTextureValue());
technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit());
}
@Override
public void write(JmeExporter ex) throws IOException {

@ -34,7 +34,6 @@ package com.jme3.material;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetManager;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.bounding.BoundingSphere;
import com.jme3.export.*;
import com.jme3.light.*;
import com.jme3.material.RenderState.BlendMode;
@ -45,18 +44,16 @@ import com.jme3.math.*;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.UniformBindingManager;
import com.jme3.shader.VarType;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.ListMap;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
@ -80,7 +77,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
private static final Logger logger = Logger.getLogger(Material.class.getName());
private static final RenderState additiveLight = new RenderState();
private static final RenderState depthOnly = new RenderState();
private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
static {
depthOnly.setDepthTest(true);
@ -105,10 +101,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
private int sortingId = -1;
private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
//Env textures units
int irrUnit = -1;
int pemUnit = -1;
public Material(MaterialDef def) {
if (def == null) {
throw new NullPointerException("Material definition cannot be null");
@ -180,22 +172,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
* @return The sorting ID used for sorting geometries for rendering.
*/
public int getSortId() {
Technique t = getActiveTechnique();
if (sortingId == -1 && t != null && t.getShader() != null) {
int texId = -1;
if (sortingId == -1 && technique != null) {
sortingId = technique.getSortId() << 16;
int texturesSortId = 17;
for (int i = 0; i < paramValues.size(); i++) {
MatParam param = paramValues.getValue(i);
if (param instanceof MatParamTexture) {
MatParamTexture tex = (MatParamTexture) param;
if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
if (texId == -1) {
texId = 0;
}
texId += tex.getTextureValue().getImage().getId() % 0xff;
}
if (!param.getVarType().isTextureType()) {
continue;
}
Texture texture = (Texture) param.getValue();
if (texture == null) {
continue;
}
Image image = texture.getImage();
if (image == null) {
continue;
}
int textureId = image.getId();
if (textureId == -1) {
textureId = 0;
}
texturesSortId = texturesSortId * 23 + textureId;
}
sortingId = texId + t.getShader().getId() * 1000;
sortingId |= texturesSortId & 0xFFFF;
}
return sortingId;
}
@ -220,6 +219,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
mat.paramValues.put(entry.getKey(), entry.getValue().clone());
}
mat.sortingId = -1;
return mat;
} catch (CloneNotSupportedException ex) {
throw new AssertionError(ex);
@ -449,7 +450,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
*
* @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
*/
public ListMap getParamsMap() {
public ListMap<String, MatParam> getParamsMap() {
return paramValues;
}
@ -510,26 +511,22 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
paramValues.remove(name);
if (matParam instanceof MatParamTexture) {
int texUnit = ((MatParamTexture) matParam).getUnit();
removeTexUnit(texUnit);
nextTexUnit--;
for (MatParam param : paramValues.values()) {
if (param instanceof MatParamTexture) {
MatParamTexture texParam = (MatParamTexture) param;
if (texParam.getUnit() > texUnit) {
texParam.setUnit(texParam.getUnit() - 1);
}
}
}
sortingId = -1;
}
if (technique != null) {
technique.notifyParamChanged(name, null, null);
}
}
protected void removeTexUnit(int texUnit) {
nextTexUnit--;
for (MatParam param : paramValues.values()) {
if (param instanceof MatParamTexture) {
MatParamTexture texParam = (MatParamTexture) param;
if (texParam.getUnit() > texUnit) {
texParam.setUnit(texParam.getUnit() - 1);
}
}
}
sortingId = -1;
}
/**
* Set a texture parameter.
*
@ -704,304 +701,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
setParam(name, VarType.Vector4, value);
}
private LightProbe extractIndirectLights(LightList lightList, boolean removeLights) {
ambientLightColor.set(0, 0, 0, 1);
LightProbe probe = null;
for (int j = 0; j < lightList.size(); j++) {
Light l = lightList.get(j);
if (l instanceof AmbientLight) {
ambientLightColor.addLocal(l.getColor());
if(removeLights){
lightList.remove(l);
j--;
}
}
if (l instanceof LightProbe) {
probe = (LightProbe)l;
if(removeLights){
lightList.remove(l);
j--;
}
}
}
ambientLightColor.a = 1.0f;
return probe;
}
private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
Mesh mesh = geom.getMesh();
int lodLevel = geom.getLodLevel();
if (geom instanceof InstancedGeometry) {
InstancedGeometry instGeom = (InstancedGeometry) geom;
int numInstances = instGeom.getActualNumInstances();
if (numInstances == 0) {
return;
}
if (renderer.getCaps().contains(Caps.MeshInstancing)) {
renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData());
} else {
throw new RendererException("Mesh instancing is not supported by the video hardware");
}
} else {
renderer.renderMesh(mesh, lodLevel, 1, null);
}
}
/**
* Uploads the lights in the light list as two uniform arrays.<br/><br/> *
* <p>
* <code>uniform vec4 g_LightColor[numLights];</code><br/> //
* g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
* g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
* 2 = Spot. <br/> <br/>
* <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
* g_LightPosition.xyz is the position of the light (for point lights)<br/>
* // or the direction of the light (for directional lights).<br/> //
* g_LightPosition.w is the inverse radius (1/r) of the light (for
* attenuation) <br/> </p>
*/
protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) {
if (numLights == 0) { // this shader does not do lighting, ignore.
return 0;
}
Uniform lightData = shader.getUniform("g_LightData");
lightData.setVector4Length(numLights * 3);//8 lights * max 3
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
lightProbeData.setVector4Length(1);
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
LightProbe lightProbe = null;
if (startIndex != 0) {
// apply additive blending for 2nd and future passes
rm.getRenderer().applyRenderState(additiveLight);
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
}else{
lightProbe = extractIndirectLights(lightList,true);
ambientColor.setValue(VarType.Vector4, ambientLightColor);
}
//If there is a lightProbe in the list we force it's 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);
//assigning new texture indexes if they have never been assigned.
if( irrUnit == -1 ){
irrUnit = nextTexUnit++;
pemUnit = nextTexUnit++;
}
rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
lightProbeIrrMap.setValue(VarType.Int, irrUnit);
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit);
} else {
//Disable IBL for this pass
lightProbeData.setVector4InArray(0,0,0,-1, 0);
}
int lightDataIndex = 0;
TempVars vars = TempVars.get();
Vector4f tmpVec = vars.vect4f1;
int curIndex;
int endIndex = numLights + startIndex;
boolean useIBL = false;
for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
Light l = lightList.get(curIndex);
if(l.getType() == Light.Type.Ambient){
endIndex++;
continue;
}
ColorRGBA color = l.getColor();
//Color
if(l.getType() != Light.Type.Probe){
lightData.setVector4InArray(color.getRed(),
color.getGreen(),
color.getBlue(),
l.getType().getId(),
lightDataIndex);
lightDataIndex++;
}
switch (l.getType()) {
case Directional:
DirectionalLight dl = (DirectionalLight) l;
Vector3f dir = dl.getDirection();
//Data directly sent in view space to avoid a matrix mult for each pixel
tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0,0,0,0, lightDataIndex);
lightDataIndex++;
break;
case Point:
PointLight pl = (PointLight) l;
Vector3f pos = pl.getPosition();
float invRadius = pl.getInvRadius();
tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0,0,0,0, lightDataIndex);
lightDataIndex++;
break;
case Spot:
SpotLight sl = (SpotLight) l;
Vector3f pos2 = sl.getPosition();
Vector3f dir2 = sl.getDirection();
float invRange = sl.getInvSpotRange();
float spotAngleCos = sl.getPackedAngleCos();
tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
lightDataIndex++;
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
lightDataIndex++;
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
}
vars.release();
//Padding of unsued buffer space
while(lightDataIndex < numLights * 3) {
lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
lightDataIndex++;
}
return curIndex;
}
protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) {
Renderer r = rm.getRenderer();
Uniform lightDir = shader.getUniform("g_LightDirection");
Uniform lightColor = shader.getUniform("g_LightColor");
Uniform lightPos = shader.getUniform("g_LightPosition");
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
boolean isFirstLight = true;
boolean isSecondLight = false;
for (int i = 0; i < lightList.size(); i++) {
Light l = lightList.get(i);
if (l instanceof AmbientLight || l instanceof LightProbe ) {
continue;
}
if (isFirstLight) {
// set ambient color for first light only
extractIndirectLights(lightList, false);
ambientColor.setValue(VarType.Vector4, ambientLightColor);
isFirstLight = false;
isSecondLight = true;
} else if (isSecondLight) {
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
// apply additive blending for 2nd and future lights
r.applyRenderState(additiveLight);
isSecondLight = false;
}
TempVars vars = TempVars.get();
Quaternion tmpLightDirection = vars.quat1;
Vector4f tmpLightPosition = vars.vect4f2;
ColorRGBA tmpLightColor = vars.color;
Vector4f tmpVec = vars.vect4f1;
ColorRGBA color = l.getColor();
tmpLightColor.set(color);
tmpLightColor.a = l.getType().getId();
lightColor.setValue(VarType.Vector4, tmpLightColor);
switch (l.getType()) {
case Directional:
DirectionalLight dl = (DirectionalLight) l;
Vector3f dir = dl.getDirection();
//FIXME : there is an inconstency here due to backward
//compatibility of the lighting shader.
//The directional light direction is passed in the
//LightPosition uniform. The lighting shader needs to be
//reworked though in order to fix this.
tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpLightPosition);
lightPos.setValue(VarType.Vector4, tmpLightPosition);
tmpLightDirection.set(0, 0, 0, 0);
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
case Point:
PointLight pl = (PointLight) l;
Vector3f pos = pl.getPosition();
float invRadius = pl.getInvRadius();
tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpLightPosition);
lightPos.setValue(VarType.Vector4, tmpLightPosition);
tmpLightDirection.set(0, 0, 0, 0);
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
case Spot:
SpotLight sl = (SpotLight) l;
Vector3f pos2 = sl.getPosition();
Vector3f dir2 = sl.getDirection();
float invRange = sl.getInvSpotRange();
float spotAngleCos = sl.getPackedAngleCos();
tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpLightPosition);
lightPos.setValue(VarType.Vector4, tmpLightPosition);
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0);
if (technique.getDef().getLightSpace() == TechniqueDef.LightSpace.Legacy) {
//Legacy kept for backward compatibility.
//We transform the spot direction in view space here to save 5 varying later in the lighting shader
//one vec4 less and a vec4 that becomes a vec3
//the downside is that spotAngleCos decoding happens now in the frag shader.
transposeLightDataToSpace(TechniqueDef.LightSpace.View, rm, tmpVec);
} else {
transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec);
}
tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
vars.release();
r.setShader(shader);
renderMeshFromGeometry(r, g);
}
if (isFirstLight) {
// Either there are no lights at all, or only ambient lights.
// Render a dummy "normal light" so we can see the ambient color.
extractIndirectLights(lightList, false);
ambientColor.setValue(VarType.Vector4, ambientLightColor);
lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
lightPos.setValue(VarType.Vector4, nullDirLight);
r.setShader(shader);
renderMeshFromGeometry(r, g);
}
}
private void transposeLightDataToSpace(TechniqueDef.LightSpace space, RenderManager rm, Vector4f store){
if(space == TechniqueDef.LightSpace.View){
rm.getCurrentCamera().getViewMatrix().mult(store, store);
}
};
/**
* Select the technique to use for rendering this material.
* <p>
@ -1030,9 +729,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
Technique tech = techniques.get(name);
// When choosing technique, we choose one that
// supports all the caps.
EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
if (tech == null) {
EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
if (name.equals("Default")) {
List<TechniqueDef> techDefs = def.getDefaultTechniques();
if (techDefs == null || techDefs.isEmpty()) {
@ -1081,17 +779,70 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
}
technique = tech;
tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager);
tech.notifyTechniqueSwitched();
// shader was changed
sortingId = -1;
}
private void autoSelectTechnique(RenderManager rm) {
if (technique == null) {
selectTechnique("Default", rm);
private int updateShaderMaterialParameters(Renderer renderer, Shader shader, List<MatParamOverride> overrides) {
int unit = 0;
if (overrides != null) {
for (MatParamOverride override : overrides) {
VarType type = override.getVarType();
MatParam paramDef = def.getMaterialParam(override.getName());
if (paramDef == null || paramDef.getVarType() != type || !override.isEnabled()) {
continue;
}
Uniform uniform = shader.getUniform(override.getPrefixedName());
if (override.getValue() != null) {
if (type.isTextureType()) {
renderer.setTexture(unit, (Texture) override.getValue());
uniform.setValue(VarType.Int, unit);
unit++;
} else {
uniform.setValue(type, override.getValue());
}
} else {
uniform.clearValue();
}
}
}
for (int i = 0; i < paramValues.size(); i++) {
MatParam param = paramValues.getValue(i);
VarType type = param.getVarType();
Uniform uniform = shader.getUniform(param.getPrefixedName());
if (uniform.isSetByCurrentMaterial()) {
continue;
}
if (type.isTextureType()) {
renderer.setTexture(unit, (Texture) param.getValue());
uniform.setValue(VarType.Int, unit);
unit++;
} else {
uniform.setValue(type, param.getValue());
}
}
//TODO HACKY HACK remove this when texture unit is handled by the uniform.
return unit;
}
private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
if (renderManager.getForcedRenderState() != null) {
renderer.applyRenderState(renderManager.getForcedRenderState());
} else {
technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps(), rm);
if (techniqueDef.getRenderState() != null) {
renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
} else {
renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
}
}
}
@ -1102,20 +853,23 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
* used for rendering, there won't be any delay since the material has
* been already been setup for rendering.
*
* @param rm The render manager to preload for
* @param renderManager The render manager to preload for
*/
public void preload(RenderManager rm) {
autoSelectTechnique(rm);
Renderer r = rm.getRenderer();
TechniqueDef techDef = technique.getDef();
public void preload(RenderManager renderManager) {
if (technique == null) {
selectTechnique("Default", renderManager);
}
TechniqueDef techniqueDef = technique.getDef();
Renderer renderer = renderManager.getRenderer();
EnumSet<Caps> rendererCaps = renderer.getCaps();
Collection<MatParam> params = paramValues.values();
for (MatParam param : params) {
param.apply(r, technique);
if (techniqueDef.isNoRender()) {
return;
}
r.setShader(technique.getShader());
Shader shader = technique.makeCurrent(renderManager, null, null, rendererCaps);
updateShaderMaterialParameters(renderer, shader, null);
renderManager.getRenderer().setShader(shader);
}
private void clearUniformsSetByCurrent(Shader shader) {
@ -1197,83 +951,47 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
* </ul>
* </ul>
*
* @param geom The geometry to render
* @param geometry The geometry to render
* @param lights Presorted and filtered light list to use for rendering
* @param rm The render manager requesting the rendering
* @param renderManager The render manager requesting the rendering
*/
public void render(Geometry geom, LightList lights, RenderManager rm) {
autoSelectTechnique(rm);
TechniqueDef techDef = technique.getDef();
if (techDef.isNoRender()) return;
Renderer r = rm.getRenderer();
if (rm.getForcedRenderState() != null) {
r.applyRenderState(rm.getForcedRenderState());
} else {
if (techDef.getRenderState() != null) {
r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
} else {
r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
}
public void render(Geometry geometry, LightList lights, RenderManager renderManager) {
if (technique == null) {
selectTechnique("Default", renderManager);
}
TechniqueDef techniqueDef = technique.getDef();
Renderer renderer = renderManager.getRenderer();
EnumSet<Caps> rendererCaps = renderer.getCaps();
// update camera and world matrices
// NOTE: setWorldTransform should have been called already
if (techniqueDef.isNoRender()) {
return;
}
// reset unchanged uniform flag
clearUniformsSetByCurrent(technique.getShader());
rm.updateUniformBindings(technique.getWorldBindUniforms());
// Apply render state
updateRenderState(renderManager, renderer, techniqueDef);
// Get world overrides
List<MatParamOverride> overrides = geometry.getWorldMatParamOverrides();
// setup textures and uniforms
for (int i = 0; i < paramValues.size(); i++) {
MatParam param = paramValues.getValue(i);
param.apply(r, technique);
}
// Select shader to use
Shader shader = technique.makeCurrent(renderManager, overrides, lights, rendererCaps);
Shader shader = technique.getShader();
// Begin tracking which uniforms were changed by material.
clearUniformsSetByCurrent(shader);
// Set uniform bindings
renderManager.updateUniformBindings(shader);
// send lighting information, if needed
switch (techDef.getLightMode()) {
case Disable:
break;
case SinglePass:
assert technique.getDef().getLightSpace()!= null;
int nbRenderedLights = 0;
resetUniformsNotSetByCurrent(shader);
if (lights.size() == 0) {
updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, 0);
r.setShader(shader);
renderMeshFromGeometry(r, geom);
} else {
while (nbRenderedLights < lights.size()) {
nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, nbRenderedLights);
r.setShader(shader);
renderMeshFromGeometry(r, geom);
}
}
return;
case FixedPipeline:
throw new IllegalArgumentException("OpenGL1 is not supported");
case MultiPass:
assert technique.getDef().getLightSpace()!= null;
// NOTE: Special case!
resetUniformsNotSetByCurrent(shader);
renderMultipassLighting(shader, geom, lights, rm);
// very important, notice the return statement!
return;
}
// Set material parameters
//TODO RRemove the unit when texture units are handled in the Uniform
int unit = updateShaderMaterialParameters(renderer, shader, geometry.getWorldMatParamOverrides());
// upload and bind shader
// any unset uniforms will be set to 0
// Clear any uniforms not changed by material.
resetUniformsNotSetByCurrent(shader);
r.setShader(shader);
renderMeshFromGeometry(r, geom);
// Delegate rendering to the technique
technique.render(renderManager, shader, geometry, lights, unit);
}
/**
@ -1299,6 +1017,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
oc.writeStringSavableMap(paramValues, "parameters", null);
}
@Override
public String toString() {
return "Material[name=" + name +
", def=" + def.getName() +
", tech=" + technique.getDef().getName() +
"]";
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);

@ -822,6 +822,9 @@ public class RenderState implements Cloneable, Savable {
* @param lineWidth the line width.
*/
public void setLineWidth(float lineWidth) {
if (lineWidth < 1f) {
throw new IllegalArgumentException("lineWidth must be greater than or equal to 1.0");
}
this.lineWidth = lineWidth;
this.applyLineWidth = true;
cachedHashCode = -1;

@ -31,27 +31,30 @@
*/
package com.jme3.material;
import com.jme3.material.logic.TechniqueDefLogic;
import com.jme3.asset.AssetManager;
import com.jme3.light.LightList;
import com.jme3.material.TechniqueDef.LightMode;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.shader.*;
import com.jme3.scene.Geometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.VarType;
import com.jme3.util.ListMap;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.logging.Logger;
/**
* Represents a technique instance.
*/
public class Technique /* implements Savable */ {
public final class Technique {
private static final Logger logger = Logger.getLogger(Technique.class.getName());
private TechniqueDef def;
private Material owner;
private ArrayList<Uniform> worldBindUniforms;
private DefineList defines;
private Shader shader;
private boolean needReload = true;
private final TechniqueDef def;
private final Material owner;
private final DefineList paramDefines;
private final DefineList dynamicDefines;
/**
* Creates a new technique instance that implements the given
@ -63,14 +66,8 @@ public class Technique /* implements Savable */ {
public Technique(Material owner, TechniqueDef def) {
this.owner = owner;
this.def = def;
this.worldBindUniforms = new ArrayList<Uniform>();
this.defines = new DefineList();
}
/**
* Serialization only. Do not use.
*/
public Technique() {
this.paramDefines = def.createDefineList();
this.dynamicDefines = def.createDefineList();
}
/**
@ -85,157 +82,117 @@ public class Technique /* implements Savable */ {
}
/**
* Returns the shader currently used by this technique instance.
* <p>
* Shaders are typically loaded dynamically when the technique is first
* used, therefore, this variable will most likely be null most of the time.
*
* @return the shader currently used by this technique instance.
* Called by the material to tell the technique a parameter was modified.
* Specify <code>null</code> for value if the param is to be cleared.
*/
public Shader getShader() {
return shader;
final void notifyParamChanged(String paramName, VarType type, Object value) {
Integer defineId = def.getShaderParamDefineId(paramName);
if (defineId == null) {
return;
}
paramDefines.set(defineId, type, value);
}
/**
* Returns a list of uniforms that implements the world parameters
* that were requested by the material definition.
*
* @return a list of uniforms implementing the world parameters.
* Called by the material to tell the technique that it has been made
* current.
* The technique updates dynamic defines based on the
* currently set material parameters.
*/
public List<Uniform> getWorldBindUniforms() {
return worldBindUniforms;
final void notifyTechniqueSwitched() {
ListMap<String, MatParam> paramMap = owner.getParamsMap();
paramDefines.clear();
for (int i = 0; i < paramMap.size(); i++) {
MatParam param = paramMap.getValue(i);
notifyParamChanged(param.getName(), param.getVarType(), param.getValue());
}
}
/**
* Called by the material to tell the technique a parameter was modified.
* Specify <code>null</code> for value if the param is to be cleared.
* Called by the material to determine which shader to use for rendering.
*
* The {@link TechniqueDefLogic} is used to determine the shader to use
* based on the {@link LightMode}.
*
* @param renderManager The render manager for which the shader is to be selected.
* @param rendererCaps The renderer capabilities which the shader should support.
* @return A compatible shader.
*/
void notifyParamChanged(String paramName, VarType type, Object value) {
// Check if there's a define binding associated with this
// parameter.
String defineName = def.getShaderParamDefine(paramName);
if (defineName != null) {
// There is a define. Change it on the define list.
// The "needReload" variable will determine
// if the shader will be reloaded when the material
// is rendered.
if (value == null) {
// Clear the define.
needReload = defines.remove(defineName) || needReload;
} else {
// Set the define.
needReload = defines.set(defineName, type, value) || needReload;
Shader makeCurrent(RenderManager renderManager, List<MatParamOverride> overrides,
LightList lights, EnumSet<Caps> rendererCaps) {
TechniqueDefLogic logic = def.getLogic();
AssetManager assetManager = owner.getMaterialDef().getAssetManager();
dynamicDefines.clear();
dynamicDefines.setAll(paramDefines);
if (overrides != null) {
for (MatParamOverride override : overrides) {
if (!override.isEnabled()) {
continue;
}
Integer defineId = def.getShaderParamDefineId(override.name);
if (defineId != null) {
if (def.getDefineIdType(defineId) == override.type) {
dynamicDefines.set(defineId, override.type, override.value);
}
}
}
}
}
void updateUniformParam(String paramName, VarType type, Object value) {
if (paramName == null) {
throw new IllegalArgumentException();
}
Uniform u = shader.getUniform(paramName);
switch (type) {
case TextureBuffer:
case Texture2D: // fall intentional
case Texture3D:
case TextureArray:
case TextureCubeMap:
case Int:
u.setValue(VarType.Int, value);
break;
default:
u.setValue(type, value);
break;
}
return logic.makeCurrent(assetManager, renderManager, rendererCaps, lights, dynamicDefines);
}
/**
* Returns true if the technique must be reloaded.
* <p>
* If a technique needs to reload, then the {@link Material} should
* call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this
* technique.
* Render the technique according to its {@link TechniqueDefLogic}.
*
* @return true if the technique must be reloaded.
* @param renderManager The render manager to perform the rendering against.
* @param shader The shader that was selected in
* {@link #makeCurrent(com.jme3.renderer.RenderManager, java.util.EnumSet)}.
* @param geometry The geometry to render
* @param lights Lights which influence the geometry.
*/
public boolean isNeedReload() {
return needReload;
void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
TechniqueDefLogic logic = def.getLogic();
logic.render(renderManager, shader, geometry, lights, lastTexUnit);
}
/**
* Prepares the technique for use by loading the shader and setting
* the proper defines based on material parameters.
* Get the {@link DefineList} for dynamic defines.
*
* Dynamic defines are used to implement material parameter -> define
* bindings as well as {@link TechniqueDefLogic} specific functionality.
*
* @param assetManager The asset manager to use for loading shaders.
* @return all dynamic defines.
*/
public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet<Caps> rendererCaps, RenderManager rm) {
if (techniqueSwitched) {
if (defines.update(owner.getParamsMap(), def)) {
needReload = true;
}
if (getDef().getLightMode() == TechniqueDef.LightMode.SinglePass) {
defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, true);
defines.set("NB_LIGHTS", VarType.Int, rm.getSinglePassLightBatchSize() * 3);
} else {
defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, null);
}
}
if (needReload) {
loadShader(assetManager,rendererCaps);
}
}
private void loadShader(AssetManager manager,EnumSet<Caps> rendererCaps) {
ShaderKey key = new ShaderKey(getAllDefines(),def.getShaderProgramLanguages(),def.getShaderProgramNames());
if (getDef().isUsingShaderNodes()) {
manager.getShaderGenerator(rendererCaps).initialize(this);
key.setUsesShaderNodes(true);
}
shader = manager.loadShader(key);
// register the world bound uniforms
worldBindUniforms.clear();
if (def.getWorldBindings() != null) {
for (UniformBinding binding : def.getWorldBindings()) {
Uniform uniform = shader.getUniform("g_" + binding.name());
uniform.setBinding(binding);
worldBindUniforms.add(uniform);
}
}
needReload = false;
public DefineList getDynamicDefines() {
return dynamicDefines;
}
/**
* Computes the define list
* @return the complete define list
* @return nothing.
*
* @deprecated Preset defines are precompiled into
* {@link TechniqueDef#getShaderPrologue()}, whereas
* dynamic defines are available via {@link #getParamDefines()}.
*/
@Deprecated
public DefineList getAllDefines() {
DefineList allDefines = new DefineList();
allDefines.addFrom(def.getShaderPresetDefines());
allDefines.addFrom(defines);
return allDefines;
throw new UnsupportedOperationException();
}
/*
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(def, "def", null);
oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null);
oc.write(defines, "defines", null);
oc.write(shader, "shader", null);
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
def = (TechniqueDef) ic.readSavable("def", null);
worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null);
defines = (DefineList) ic.readSavable("defines", null);
shader = (Shader) ic.readSavable("shader", null);
/**
* Compute the sort ID. Similar to {@link Object#hashCode()} but used
* for sorting geometries for rendering.
*
* @return the sort ID for this technique instance.
*/
public int getSortId() {
int hash = 17;
hash = hash * 23 + def.getSortId();
hash = hash * 23 + paramDefines.hashCode();
return hash;
}
*/
}

@ -31,9 +31,12 @@
*/
package com.jme3.material;
import com.jme3.material.logic.TechniqueDefLogic;
import com.jme3.asset.AssetManager;
import com.jme3.export.*;
import com.jme3.renderer.Caps;
import com.jme3.shader.*;
import com.jme3.shader.Shader.ShaderType;
import java.io.IOException;
import java.util.*;
@ -78,6 +81,16 @@ public class TechniqueDef implements Savable {
*/
MultiPass,
/**
* Enable light rendering by using a single pass, and also uses Image based lighting for global lighting
* Usually used for PBR
* <p>
* An array of light positions and light colors is passed to the shader
* containing the world light list for the geometry being rendered.
* Also Light probes are passed to the shader.
*/
SinglePassAndImageBased,
/**
* @deprecated OpenGL1 is not supported anymore
*/
@ -102,11 +115,17 @@ public class TechniqueDef implements Savable {
private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
private String name;
private int sortId;
private EnumMap<Shader.ShaderType,String> shaderLanguages;
private EnumMap<Shader.ShaderType,String> shaderNames;
private DefineList presetDefines;
private String shaderPrologue;
private ArrayList<String> defineNames;
private ArrayList<VarType> defineTypes;
private HashMap<String, Integer> paramToDefineId;
private final HashMap<DefineList, Shader> definesToShaderMap;
private boolean usesNodes = false;
private List<ShaderNode> shaderNodes;
private ShaderGenerationInfo shaderGenerationInfo;
@ -115,10 +134,10 @@ public class TechniqueDef implements Savable {
private RenderState renderState;
private RenderState forcedRenderState;
private LightMode lightMode = LightMode.Disable;
private LightMode lightMode = LightMode.Disable;
private ShadowMode shadowMode = ShadowMode.Disable;
private TechniqueDefLogic logic;
private HashMap<String, String> defineParams;
private ArrayList<UniformBinding> worldBinds;
//The space in which the light should be transposed before sending to the shader.
private LightSpace lightSpace;
@ -131,17 +150,30 @@ public class TechniqueDef implements Savable {
* @param name The name of the technique, should be set to <code>null</code>
* for default techniques.
*/
public TechniqueDef(String name){
public TechniqueDef(String name, int sortId){
this();
this.sortId = sortId;
this.name = name == null ? "Default" : name;
}
/**
* Serialization only. Do not use.
*/
public TechniqueDef(){
shaderLanguages=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
shaderNames=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
public TechniqueDef() {
shaderLanguages = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
shaderNames = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
defineNames = new ArrayList<String>();
defineTypes = new ArrayList<VarType>();
paramToDefineId = new HashMap<String, Integer>();
definesToShaderMap = new HashMap<DefineList, Shader>();
}
/**
* @return A unique sort ID.
* No other technique definition can have the same ID.
*/
public int getSortId() {
return sortId;
}
/**
@ -182,6 +214,14 @@ public class TechniqueDef implements Savable {
}
}
public void setLogic(TechniqueDefLogic logic) {
this.logic = logic;
}
public TechniqueDefLogic getLogic() {
return logic;
}
/**
* Returns the shadow mode.
* @return the shadow mode.
@ -243,14 +283,6 @@ public class TechniqueDef implements Savable {
return noRender;
}
/**
* @deprecated jME3 always requires shaders now
*/
@Deprecated
public boolean isUsingShaders(){
return true;
}
/**
* Returns true if this technique uses Shader Nodes, false otherwise.
*
@ -292,32 +324,22 @@ public class TechniqueDef implements Savable {
requiredCaps.add(fragCap);
}
/**
* Sets the shaders that this technique definition will use.
* Set a string which is prepended to every shader used by this technique.
*
* @param shaderNames EnumMap containing all shader names for this stage
* @param shaderLanguages EnumMap containing all shader languages for this stage
* Typically this is used for preset defines.
*
* @param shaderPrologue The prologue to append before the technique's shaders.
*/
public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
requiredCaps.clear();
for (Shader.ShaderType shaderType : shaderNames.keySet()) {
String language = shaderLanguages.get(shaderType);
String shaderFile = shaderNames.get(shaderType);
this.shaderLanguages.put(shaderType, language);
this.shaderNames.put(shaderType, shaderFile);
Caps vertCap = Caps.valueOf(language);
requiredCaps.add(vertCap);
public void setShaderPrologue(String shaderPrologue) {
this.shaderPrologue = shaderPrologue;
}
if (shaderType.equals(Shader.ShaderType.Geometry)) {
requiredCaps.add(Caps.GeometryShader);
} else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
requiredCaps.add(Caps.TesselationShader);
}
}
/**
* @return the shader prologue which is prepended to every shader.
*/
public String getShaderPrologue() {
return shaderPrologue;
}
/**
@ -329,10 +351,32 @@ public class TechniqueDef implements Savable {
* @see #addShaderParamDefine(java.lang.String, java.lang.String)
*/
public String getShaderParamDefine(String paramName){
if (defineParams == null) {
Integer defineId = paramToDefineId.get(paramName);
if (defineId != null) {
return defineNames.get(defineId);
} else {
return null;
}
return defineParams.get(paramName);
}
/**
* Get the define ID for a given material parameter.
*
* @param paramName The parameter name to look up
* @return The define ID, or null if not found.
*/
public Integer getShaderParamDefineId(String paramName) {
return paramToDefineId.get(paramName);
}
/**
* Get the type of a particular define.
*
* @param defineId The define ID to lookup.
* @return The type of the define, or null if not found.
*/
public VarType getDefineIdType(int defineId) {
return defineId < defineTypes.size() ? defineTypes.get(defineId) : null;
}
/**
@ -340,49 +384,153 @@ public class TechniqueDef implements Savable {
* <p>
* Any time the material parameter on the parent material is altered,
* the appropriate define on the technique will be modified as well.
* See the method
* {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
* on the exact details of how the material parameter changes the define.
* When set, the material parameter will be mapped to an integer define,
* typically <code>1</code> if it is set, unless it is an integer or a float,
* in which case it will converted into an integer.
*
* @param paramName The name of the material parameter to link to.
* @param paramType The type of the material parameter to link to.
* @param defineName The name of the define parameter, e.g. USE_LIGHTING
*/
public void addShaderParamDefine(String paramName, String defineName){
if (defineParams == null) {
defineParams = new HashMap<String, String>();
public void addShaderParamDefine(String paramName, VarType paramType, String defineName){
int defineId = defineNames.size();
if (defineId >= DefineList.MAX_DEFINES) {
throw new IllegalStateException("Cannot have more than " +
DefineList.MAX_DEFINES + " defines on a technique.");
}
defineParams.put(paramName, defineName);
paramToDefineId.put(paramName, defineId);
defineNames.add(defineName);
defineTypes.add(paramType);
}
/**
* Returns the {@link DefineList} for the preset defines.
* Add an unmapped define which can only be set by define ID.
*
* @return the {@link DefineList} for the preset defines.
* Unmapped defines are used by technique renderers to
* configure the shader internally before rendering.
*
* @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
* @param defineName The define name to create
* @return The define ID of the created define
*/
public DefineList getShaderPresetDefines() {
return presetDefines;
public int addShaderUnmappedDefine(String defineName, VarType defineType) {
int defineId = defineNames.size();
if (defineId >= DefineList.MAX_DEFINES) {
throw new IllegalStateException("Cannot have more than " +
DefineList.MAX_DEFINES + " defines on a technique.");
}
defineNames.add(defineName);
defineTypes.add(defineType);
return defineId;
}
/**
* Adds a preset define.
* <p>
* Preset defines do not depend upon any parameters to be activated,
* they are always passed to the shader as long as this technique is used.
* Get the names of all defines declared on this technique definition.
*
* @param defineName The name of the define parameter, e.g. USE_LIGHTING
* @param type The type of the define. See
* {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
* to see why it matters.
* The defines are returned in order of declaration.
*
* @return the names of all defines declared.
*/
public String[] getDefineNames() {
return defineNames.toArray(new String[0]);
}
/**
* Get the types of all defines declared on this technique definition.
*
* The types are returned in order of declaration.
*
* @return the types of all defines declared.
*/
public VarType[] getDefineTypes() {
return defineTypes.toArray(new VarType[0]);
}
/**
* Create a define list with the size matching the number
* of defines on this technique.
*
* @return a define list with the size matching the number
* of defines on this technique.
*/
public DefineList createDefineList() {
return new DefineList(defineNames.size());
}
private Shader loadShader(AssetManager assetManager, EnumSet<Caps> rendererCaps, DefineList defines) {
StringBuilder sb = new StringBuilder();
sb.append(shaderPrologue);
defines.generateSource(sb, defineNames, defineTypes);
String definesSourceCode = sb.toString();
Shader shader;
if (isUsingShaderNodes()) {
ShaderGenerator shaderGenerator = assetManager.getShaderGenerator(rendererCaps);
if (shaderGenerator == null) {
throw new UnsupportedOperationException("ShaderGenerator was not initialized, "
+ "make sure assetManager.getGenerator(caps) has been called");
}
shaderGenerator.initialize(this);
shader = shaderGenerator.generateShader(definesSourceCode);
} else {
shader = new Shader();
for (ShaderType type : ShaderType.values()) {
String language = shaderLanguages.get(type);
String shaderSourceAssetName = shaderNames.get(type);
if (language == null || shaderSourceAssetName == null) {
continue;
}
String shaderSourceCode = (String) assetManager.loadAsset(shaderSourceAssetName);
shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
}
}
if (getWorldBindings() != null) {
for (UniformBinding binding : getWorldBindings()) {
shader.addUniformBinding(binding);
}
}
return shader;
}
public Shader getShader(AssetManager assetManager, EnumSet<Caps> rendererCaps, DefineList defines) {
Shader shader = definesToShaderMap.get(defines);
if (shader == null) {
shader = loadShader(assetManager, rendererCaps, defines);
definesToShaderMap.put(defines.deepClone(), shader);
}
return shader;
}
/**
* Sets the shaders that this technique definition will use.
*
* @param value The value of the define
* @param shaderNames EnumMap containing all shader names for this stage
* @param shaderLanguages EnumMap containing all shader languages for this stage
*/
public void addShaderPresetDefine(String defineName, VarType type, Object value){
if (presetDefines == null) {
presetDefines = new DefineList();
public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
requiredCaps.clear();
for (Shader.ShaderType shaderType : shaderNames.keySet()) {
String language = shaderLanguages.get(shaderType);
String shaderFile = shaderNames.get(shaderType);
this.shaderLanguages.put(shaderType, language);
this.shaderNames.put(shaderType, shaderFile);
Caps vertCap = Caps.valueOf(language);
requiredCaps.add(vertCap);
if (shaderType.equals(Shader.ShaderType.Geometry)) {
requiredCaps.add(Caps.GeometryShader);
} else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
requiredCaps.add(Caps.TesselationShader);
}
}
presetDefines.set(defineName, type, value);
}
/**
@ -486,7 +634,7 @@ public class TechniqueDef implements Savable {
oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null);
oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null);
oc.write(presetDefines, "presetDefines", null);
oc.write(shaderPrologue, "shaderPrologue", null);
oc.write(lightMode, "lightMode", LightMode.Disable);
oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
oc.write(renderState, "renderState", null);
@ -509,7 +657,7 @@ public class TechniqueDef implements Savable {
shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null));
shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null));
presetDefines = (DefineList) ic.readSavable("presetDefines", null);
shaderPrologue = ic.readString("shaderPrologue", null);
lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
renderState = (RenderState) ic.readSavable("renderState", null);
@ -566,10 +714,15 @@ public class TechniqueDef implements Savable {
this.shaderGenerationInfo = shaderGenerationInfo;
}
//todo: make toString return something usefull
@Override
public String toString() {
return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}';
return "TechniqueDef[name=" + name
+ ", requiredCaps=" + requiredCaps
+ ", noRender=" + noRender
+ ", lightMode=" + lightMode
+ ", usesNodes=" + usesNodes
+ ", renderState=" + renderState
+ ", forcedRenderState=" + forcedRenderState + "]";
}
/**

@ -0,0 +1,97 @@
/*
* 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.light.*;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import java.util.EnumSet;
public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
protected final TechniqueDef techniqueDef;
public DefaultTechniqueDefLogic(TechniqueDef techniqueDef) {
this.techniqueDef = techniqueDef;
}
@Override
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
return techniqueDef.getShader(assetManager, rendererCaps, defines);
}
public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
Mesh mesh = geom.getMesh();
int lodLevel = geom.getLodLevel();
if (geom instanceof InstancedGeometry) {
InstancedGeometry instGeom = (InstancedGeometry) geom;
renderer.renderMesh(mesh, lodLevel, instGeom.getActualNumInstances(),
instGeom.getAllInstanceData());
} else {
renderer.renderMesh(mesh, lodLevel, 1, null);
}
}
protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLights, ColorRGBA ambientLightColor) {
ambientLightColor.set(0, 0, 0, 1);
for (int j = 0; j < lightList.size(); j++) {
Light l = lightList.get(j);
if (l instanceof AmbientLight) {
ambientLightColor.addLocal(l.getColor());
if (removeLights) {
lightList.remove(l);
}
}
}
ambientLightColor.a = 1.0f;
return ambientLightColor;
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
Renderer renderer = renderManager.getRenderer();
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
}
}

@ -0,0 +1,178 @@
/*
* 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.Light;
import com.jme3.light.LightList;
import com.jme3.light.PointLight;
import com.jme3.light.SpotLight;
import com.jme3.material.RenderState;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.scene.Geometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.VarType;
import com.jme3.util.TempVars;
import java.util.EnumSet;
public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
private static final RenderState ADDITIVE_LIGHT = new RenderState();
private static final Quaternion NULL_DIR_LIGHT = new Quaternion(0, -1, 0, -1);
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
static {
ADDITIVE_LIGHT.setBlendMode(RenderState.BlendMode.AlphaAdditive);
ADDITIVE_LIGHT.setDepthWrite(false);
}
public MultiPassLightingLogic(TechniqueDef techniqueDef) {
super(techniqueDef);
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
Renderer r = renderManager.getRenderer();
Uniform lightDir = shader.getUniform("g_LightDirection");
Uniform lightColor = shader.getUniform("g_LightColor");
Uniform lightPos = shader.getUniform("g_LightPosition");
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
boolean isFirstLight = true;
boolean isSecondLight = false;
getAmbientColor(lights, false, ambientLightColor);
for (int i = 0; i < lights.size(); i++) {
Light l = lights.get(i);
if (l instanceof AmbientLight) {
continue;
}
if (isFirstLight) {
// set ambient color for first light only
ambientColor.setValue(VarType.Vector4, ambientLightColor);
isFirstLight = false;
isSecondLight = true;
} else if (isSecondLight) {
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
// apply additive blending for 2nd and future lights
r.applyRenderState(ADDITIVE_LIGHT);
isSecondLight = false;
}
TempVars vars = TempVars.get();
Quaternion tmpLightDirection = vars.quat1;
Quaternion tmpLightPosition = vars.quat2;
ColorRGBA tmpLightColor = vars.color;
Vector4f tmpVec = vars.vect4f1;
ColorRGBA color = l.getColor();
tmpLightColor.set(color);
tmpLightColor.a = l.getType().getId();
lightColor.setValue(VarType.Vector4, tmpLightColor);
switch (l.getType()) {
case Directional:
DirectionalLight dl = (DirectionalLight) l;
Vector3f dir = dl.getDirection();
//FIXME : there is an inconstency here due to backward
//compatibility of the lighting shader.
//The directional light direction is passed in the
//LightPosition uniform. The lighting shader needs to be
//reworked though in order to fix this.
tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
lightPos.setValue(VarType.Vector4, tmpLightPosition);
tmpLightDirection.set(0, 0, 0, 0);
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
case Point:
PointLight pl = (PointLight) l;
Vector3f pos = pl.getPosition();
float invRadius = pl.getInvRadius();
tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
lightPos.setValue(VarType.Vector4, tmpLightPosition);
tmpLightDirection.set(0, 0, 0, 0);
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
case Spot:
SpotLight sl = (SpotLight) l;
Vector3f pos2 = sl.getPosition();
Vector3f dir2 = sl.getDirection();
float invRange = sl.getInvSpotRange();
float spotAngleCos = sl.getPackedAngleCos();
tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
lightPos.setValue(VarType.Vector4, tmpLightPosition);
//We transform the spot direction in view space here to save 5 varying later in the lighting shader
//one vec4 less and a vec4 that becomes a vec3
//the downside is that spotAngleCos decoding happens now in the frag shader.
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0);
renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
vars.release();
r.setShader(shader);
renderMeshFromGeometry(r, geometry);
}
if (isFirstLight) {
// Either there are no lights at all, or only ambient lights.
// Render a dummy "normal light" so we can see the ambient color.
ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, false, ambientLightColor));
lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
lightPos.setValue(VarType.Vector4, NULL_DIR_LIGHT);
r.setShader(shader);
renderMeshFromGeometry(r, geometry);
}
}
}

@ -0,0 +1,255 @@
/*
* 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingSphere;
import com.jme3.light.*;
import com.jme3.material.*;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.*;
import com.jme3.renderer.*;
import com.jme3.scene.Geometry;
import com.jme3.shader.*;
import com.jme3.util.TempVars;
import java.util.EnumSet;
public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic {
private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
private static final RenderState ADDITIVE_LIGHT = new RenderState();
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
//Env textures units
int irrUnit = -1;
int pemUnit = -1;
static {
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
ADDITIVE_LIGHT.setDepthWrite(false);
}
private final int singlePassLightingDefineId;
private final int nbLightsDefineId;
public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
super(techniqueDef);
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
}
@Override
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
defines.set(singlePassLightingDefineId, true);
return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
}
/**
* Uploads the lights in the light list as two uniform arrays.<br/><br/> *
* <p>
* <code>uniform vec4 g_LightColor[numLights];</code><br/> //
* g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
* g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
* 2 = Spot. <br/> <br/>
* <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
* g_LightPosition.xyz is the position of the light (for point lights)<br/>
* // or the direction of the light (for directional lights).<br/> //
* g_LightPosition.w is the inverse radius (1/r) of the light (for
* attenuation) <br/> </p>
*/
protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, int lastTexUnit) {
if (numLights == 0) { // this shader does not do lighting, ignore.
return 0;
}
Uniform lightData = shader.getUniform("g_LightData");
lightData.setVector4Length(numLights * 3);//8 lights * max 3
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
lightProbeData.setVector4Length(1);
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
LightProbe lightProbe = null;
if (startIndex != 0) {
// apply additive blending for 2nd and future passes
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
}else{
lightProbe = extractIndirectLights(lightList,true);
ambientColor.setValue(VarType.Vector4, ambientLightColor);
}
//If there is a lightProbe in the list we force it's 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);
//assigning new texture indexes if they have never been assigned.
if( irrUnit == -1 ){
irrUnit = lastTexUnit++;
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 {
//Disable IBL for this pass
lightProbeData.setVector4InArray(0,0,0,-1, 0);
}
int lightDataIndex = 0;
TempVars vars = TempVars.get();
Vector4f tmpVec = vars.vect4f1;
int curIndex;
int endIndex = numLights + startIndex;
boolean useIBL = false;
for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
Light l = lightList.get(curIndex);
if(l.getType() == Light.Type.Ambient){
endIndex++;
continue;
}
ColorRGBA color = l.getColor();
//Color
if(l.getType() != Light.Type.Probe){
lightData.setVector4InArray(color.getRed(),
color.getGreen(),
color.getBlue(),
l.getType().getId(),
lightDataIndex);
lightDataIndex++;
}
switch (l.getType()) {
case Directional:
DirectionalLight dl = (DirectionalLight) l;
Vector3f dir = dl.getDirection();
//Data directly sent in view space to avoid a matrix mult for each pixel
tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0,0,0,0, lightDataIndex);
lightDataIndex++;
break;
case Point:
PointLight pl = (PointLight) l;
Vector3f pos = pl.getPosition();
float invRadius = pl.getInvRadius();
tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0,0,0,0, lightDataIndex);
lightDataIndex++;
break;
case Spot:
SpotLight sl = (SpotLight) l;
Vector3f pos2 = sl.getPosition();
Vector3f dir2 = sl.getDirection();
float invRange = sl.getInvSpotRange();
float spotAngleCos = sl.getPackedAngleCos();
tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
lightDataIndex++;
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
lightDataIndex++;
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
}
vars.release();
//Padding of unsued buffer space
while(lightDataIndex < numLights * 3) {
lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
lightDataIndex++;
}
return curIndex;
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
int nbRenderedLights = 0;
Renderer renderer = renderManager.getRenderer();
int batchSize = renderManager.getSinglePassLightBatchSize();
if (lights.size() == 0) {
updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
} else {
while (nbRenderedLights < lights.size()) {
nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
}
}
return;
}
protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) {
ambientLightColor.set(0, 0, 0, 1);
LightProbe probe = null;
for (int j = 0; j < lightList.size(); j++) {
Light l = lightList.get(j);
if (l instanceof AmbientLight) {
ambientLightColor.addLocal(l.getColor());
if(removeLights){
lightList.remove(l);
j--;
}
}
if (l instanceof LightProbe) {
probe = (LightProbe)l;
if(removeLights){
lightList.remove(l);
j--;
}
}
}
ambientLightColor.a = 1.0f;
return probe;
}
}

@ -0,0 +1,218 @@
/*
* 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.light.DirectionalLight;
import com.jme3.light.Light;
import com.jme3.light.LightList;
import com.jme3.light.PointLight;
import com.jme3.light.SpotLight;
import com.jme3.material.RenderState;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.scene.Geometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.VarType;
import com.jme3.util.TempVars;
import java.util.EnumSet;
public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
private static final RenderState ADDITIVE_LIGHT = new RenderState();
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
static {
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
ADDITIVE_LIGHT.setDepthWrite(false);
}
private final int singlePassLightingDefineId;
private final int nbLightsDefineId;
public SinglePassLightingLogic(TechniqueDef techniqueDef) {
super(techniqueDef);
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
}
@Override
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
defines.set(singlePassLightingDefineId, true);
return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
}
/**
* Uploads the lights in the light list as two uniform arrays.<br/><br/> *
* <p>
* <code>uniform vec4 g_LightColor[numLights];</code><br/> //
* g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
* g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
* 2 = Spot. <br/> <br/>
* <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
* g_LightPosition.xyz is the position of the light (for point lights)<br/>
* // or the direction of the light (for directional lights).<br/> //
* g_LightPosition.w is the inverse radius (1/r) of the light (for
* attenuation) <br/> </p>
*/
protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) {
if (numLights == 0) { // this shader does not do lighting, ignore.
return 0;
}
Uniform lightData = shader.getUniform("g_LightData");
lightData.setVector4Length(numLights * 3);//8 lights * max 3
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
if (startIndex != 0) {
// apply additive blending for 2nd and future passes
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
} else {
ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, true, ambientLightColor));
}
int lightDataIndex = 0;
TempVars vars = TempVars.get();
Vector4f tmpVec = vars.vect4f1;
int curIndex;
int endIndex = numLights + startIndex;
for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
Light l = lightList.get(curIndex);
if (l.getType() == Light.Type.Ambient) {
endIndex++;
continue;
}
ColorRGBA color = l.getColor();
//Color
lightData.setVector4InArray(color.getRed(),
color.getGreen(),
color.getBlue(),
l.getType().getId(),
lightDataIndex);
lightDataIndex++;
switch (l.getType()) {
case Directional:
DirectionalLight dl = (DirectionalLight) l;
Vector3f dir = dl.getDirection();
//Data directly sent in view space to avoid a matrix mult for each pixel
tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
// tmpVec.divideLocal(tmpVec.w);
// tmpVec.normalizeLocal();
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
lightDataIndex++;
break;
case Point:
PointLight pl = (PointLight) l;
Vector3f pos = pl.getPosition();
float invRadius = pl.getInvRadius();
tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
//tmpVec.divideLocal(tmpVec.w);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
lightDataIndex++;
break;
case Spot:
SpotLight sl = (SpotLight) l;
Vector3f pos2 = sl.getPosition();
Vector3f dir2 = sl.getDirection();
float invRange = sl.getInvSpotRange();
float spotAngleCos = sl.getPackedAngleCos();
tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f);
rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
// tmpVec.divideLocal(tmpVec.w);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
lightDataIndex++;
//We transform the spot direction in view space here to save 5 varying later in the lighting shader
//one vec4 less and a vec4 that becomes a vec3
//the downside is that spotAngleCos decoding happens now in the frag shader.
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f);
rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
tmpVec.normalizeLocal();
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
lightDataIndex++;
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
}
vars.release();
//Padding of unsued buffer space
while(lightDataIndex < numLights * 3) {
lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
lightDataIndex++;
}
return curIndex;
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
int nbRenderedLights = 0;
Renderer renderer = renderManager.getRenderer();
int batchSize = renderManager.getSinglePassLightBatchSize();
if (lights.size() == 0) {
updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
} else {
while (nbRenderedLights < lights.size()) {
nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
}
}
}
}

@ -0,0 +1,157 @@
/*
* 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.light.DirectionalLight;
import com.jme3.light.Light;
import com.jme3.light.Light.Type;
import com.jme3.light.LightList;
import com.jme3.light.PointLight;
import com.jme3.light.SpotLight;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.scene.Geometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.VarType;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* Rendering logic for static pass.
*
* @author Kirill Vainer
*/
public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
private static final String DEFINE_NUM_DIR_LIGHTS = "NUM_DIR_LIGHTS";
private static final String DEFINE_NUM_POINT_LIGHTS = "NUM_POINT_LIGHTS";
private static final String DEFINE_NUM_SPOT_LIGHTS = "NUM_SPOT_LIGHTS";
private final int numDirLightsDefineId;
private final int numPointLightsDefineId;
private final int numSpotLightsDefineId;
private final ArrayList<DirectionalLight> tempDirLights = new ArrayList<DirectionalLight>();
private final ArrayList<PointLight> tempPointLights = new ArrayList<PointLight>();
private final ArrayList<SpotLight> tempSpotLights = new ArrayList<SpotLight>();
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
public StaticPassLightingLogic(TechniqueDef techniqueDef) {
super(techniqueDef);
numDirLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_DIR_LIGHTS, VarType.Int);
numPointLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_POINT_LIGHTS, VarType.Int);
numSpotLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_SPOT_LIGHTS, VarType.Int);
}
@Override
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
// TODO: if it ever changes that render isn't called
// right away with the same geometry after makeCurrent, it would be
// a problem.
// Do a radix sort.
tempDirLights.clear();
tempPointLights.clear();
tempSpotLights.clear();
for (Light light : lights) {
switch (light.getType()) {
case Directional:
tempDirLights.add((DirectionalLight) light);
break;
case Point:
tempPointLights.add((PointLight) light);
break;
case Spot:
tempSpotLights.add((SpotLight) light);
break;
}
}
defines.set(numDirLightsDefineId, tempDirLights.size());
defines.set(numPointLightsDefineId, tempPointLights.size());
defines.set(numSpotLightsDefineId, tempSpotLights.size());
return techniqueDef.getShader(assetManager, rendererCaps, defines);
}
private void updateLightListUniforms(Shader shader, LightList lights) {
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, true, ambientLightColor));
Uniform lightData = shader.getUniform("g_LightData");
int index = 0;
for (DirectionalLight light : tempDirLights) {
ColorRGBA color = light.getColor();
Vector3f dir = light.getDirection();
lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++);
lightData.setVector4InArray(dir.x, dir.y, dir.z, 1f, index++);
}
for (PointLight light : tempPointLights) {
ColorRGBA color = light.getColor();
Vector3f pos = light.getPosition();
lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++);
lightData.setVector4InArray(pos.x, pos.y, pos.z, 1f, index++);
}
for (SpotLight light : tempSpotLights) {
ColorRGBA color = light.getColor();
Vector3f pos = light.getPosition();
Vector3f dir = light.getDirection();
float invRange = light.getInvSpotRange();
float spotAngleCos = light.getPackedAngleCos();
lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++);
lightData.setVector4InArray(pos.x, pos.y, pos.z, invRange, index++);
lightData.setVector4InArray(dir.x, dir.y, dir.z, spotAngleCos, index++);
}
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
Renderer renderer = renderManager.getRenderer();
updateLightListUniforms(shader, lights);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
}
}

@ -0,0 +1,97 @@
/*
* 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.light.LightList;
import com.jme3.material.TechniqueDef.LightMode;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.UniformBinding;
import com.jme3.texture.Texture;
import java.util.EnumSet;
/**
* <code>TechniqueDefLogic</code> is used to customize how
* a material should be rendered.
*
* Typically used to implement {@link LightMode lighting modes}.
* Implementations can register
* {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines}
* in their constructor and then later set them based on the geometry
* or light environment being rendered.
*
* @author Kirill Vainer
*/
public interface TechniqueDefLogic {
/**
* Determine the shader to use for the given geometry / material combination.
*
* @param assetManager The asset manager to use for loading shader source code,
* shader nodes, and and lookup textures.
* @param renderManager The render manager for which rendering is to be performed.
* @param rendererCaps Renderer capabilities. The returned shader must
* support these capabilities.
* @param lights The lights with which the geometry shall be rendered. This
* list must not include culled lights.
* @param defines The define list used by the technique, any
* {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines}
* should be set here to change shader behavior.
*
* @return The shader to use for rendering.
*/
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines);
/**
* Requests that the <code>TechniqueDefLogic</code> renders the given geometry.
*
* Fixed material functionality such as {@link RenderState},
* {@link MatParam material parameters}, and
* {@link UniformBinding uniform bindings}
* have already been applied by the material, however,
* {@link RenderState}, {@link Uniform uniforms}, {@link Texture textures},
* can still be overriden.
*
* @param renderManager The render manager to perform the rendering against.
* * @param shader The shader that was selected by this logic in
* {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.shader.DefineList)}.
* @param geometry The geometry to render
* @param lights Lights which influence the geometry.
*/
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit);
}

@ -34,8 +34,12 @@ package com.jme3.renderer;
import com.jme3.light.DefaultLightFilter;
import com.jme3.light.LightFilter;
import com.jme3.light.LightList;
import com.jme3.material.*;
import com.jme3.math.Matrix4f;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.material.RenderState;
import com.jme3.material.Technique;
import com.jme3.material.TechniqueDef;
import com.jme3.math.*;
import com.jme3.post.SceneProcessor;
import com.jme3.profile.AppProfiler;
import com.jme3.profile.AppStep;
@ -45,13 +49,12 @@ import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.*;
import com.jme3.shader.Uniform;
import com.jme3.shader.Shader;
import com.jme3.shader.UniformBinding;
import com.jme3.shader.UniformBindingManager;
import com.jme3.system.NullRenderer;
import com.jme3.system.Timer;
import com.jme3.util.SafeArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -480,8 +483,8 @@ public class RenderManager {
* Updates the given list of uniforms with {@link UniformBinding uniform bindings}
* based on the current world state.
*/
public void updateUniformBindings(List<Uniform> params) {
uniformBindingManager.updateUniformBindings(params);
public void updateUniformBindings(Shader shader) {
uniformBindingManager.updateUniformBindings(shader);
}
/**
@ -531,6 +534,7 @@ public class RenderManager {
lightList = filteredLightList;
}
//if forcedTechnique we try to force it for render,
//if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null
//else the geom is not rendered
@ -612,7 +616,9 @@ public class RenderManager {
gm.getMaterial().preload(this);
Mesh mesh = gm.getMesh();
if (mesh != null) {
if (mesh != null
&& mesh.getVertexCount() != 0
&& mesh.getTriangleCount() != 0) {
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
renderer.updateBufferData(vb);
@ -637,8 +643,10 @@ public class RenderManager {
* <p>
* In addition to enqueuing the visible geometries, this method
* also scenes which cast or receive shadows, by putting them into the
* RenderQueue's {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue}.
* Each Spatial which has its {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode}
* RenderQueue's
* {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode)
* shadow queue}. Each Spatial which has its
* {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode}
* set to not off, will be put into the appropriate shadow queue, note that
* this process does not check for frustum culling on any
* {@link ShadowMode#Cast shadow casters}, as they don't have to be
@ -994,7 +1002,8 @@ public class RenderManager {
* (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })</li>
* <li>If any objects remained in the render queue, they are removed
* from the queue. This is generally objects added to the
* {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue}
* {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean)
* shadow queue}
* which were not rendered because of a missing shadow renderer.</li>
* </ul>
*

@ -58,6 +58,7 @@ 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+
}

@ -94,4 +94,10 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
gl4.glPatchParameter(count);
checkError();
}
@Override
public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5) {
gl3.glFramebufferTextureLayer(param1, param2, param3, param4, param5);
checkError();
}
}

@ -474,6 +474,17 @@ public final class GLRenderer implements Renderer {
{
sb.append("\t").append(cap.toString()).append("\n");
}
sb.append("\nHardware limits: \n");
for (Limits limit : Limits.values()) {
Integer value = limits.get(limit);
if (value == null) {
value = 0;
}
sb.append("\t").append(limit.name()).append(" = ")
.append(value).append("\n");
}
logger.log(Level.FINE, sb.toString());
}
@ -964,12 +975,12 @@ public final class GLRenderer implements Renderer {
gl.glUniform1i(loc, b.booleanValue() ? GL.GL_TRUE : GL.GL_FALSE);
break;
case Matrix3:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
assert fb.remaining() == 9;
gl.glUniformMatrix3(loc, false, fb);
break;
case Matrix4:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
assert fb.remaining() == 16;
gl.glUniformMatrix4(loc, false, fb);
break;
@ -978,23 +989,23 @@ public final class GLRenderer implements Renderer {
gl.glUniform1(loc, ib);
break;
case FloatArray:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
gl.glUniform1(loc, fb);
break;
case Vector2Array:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
gl.glUniform2(loc, fb);
break;
case Vector3Array:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
gl.glUniform3(loc, fb);
break;
case Vector4Array:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
gl.glUniform4(loc, fb);
break;
case Matrix4Array:
fb = (FloatBuffer) uniform.getValue();
fb = uniform.getMultiData();
gl.glUniformMatrix4(loc, false, fb);
break;
case Int:
@ -1442,11 +1453,19 @@ public final class GLRenderer implements Renderer {
setupTextureParams(0, tex);
}
glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
image.getId(),
0);
if (rb.getLayer() < 0){
glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
image.getId(),
0);
} else {
gl3.glFramebufferTextureLayer(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
image.getId(),
0,
rb.getLayer());
}
}
public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) {
@ -2681,12 +2700,15 @@ public final class GLRenderer implements Renderer {
}
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
if (mesh.getVertexCount() == 0 || mesh.getTriangleCount() == 0 || count == 0) {
return;
}
//this is kept for backward compatibility.
if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) {
if (count > 1 && !caps.contains(Caps.MeshInstancing)) {
throw new RendererException("Mesh instancing is not supported by the video hardware");
}
if (mesh.getLineWidth() != 1f && context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth();
}

@ -99,6 +99,16 @@ public class GeometryList implements Iterable<Geometry>{
return size;
}
/**
* Sets the element at the given index.
*
* @param index The index to set
* @param value The value
*/
public void set(int index, Geometry value) {
geometries[index] = value;
}
/**
* Returns the element at the given index.
*

@ -69,11 +69,12 @@ public class OpaqueComparator implements GeometryComparator {
return spat.queueDistance;
}
@Override
public int compare(Geometry o1, Geometry o2) {
Material m1 = o1.getMaterial();
Material m2 = o2.getMaterial();
int compareResult = m2.getSortId() - m1.getSortId();
int compareResult = Integer.compare(m1.getSortId(), m2.getSortId());
if (compareResult == 0){
// use the same shader.
// sort front-to-back then.

@ -76,6 +76,8 @@ public class AssetLinkNode extends Node {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// This is a change in behavior because the old version did not clone
// this list... changes to one clone would be reflected in all.
// I think that's probably undesirable. -pspeed

@ -727,6 +727,8 @@ public class BatchNode extends GeometryGroupNode {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.batches = cloner.clone(batches);
this.tmpFloat = cloner.clone(tmpFloat);
this.tmpFloatN = cloner.clone(tmpFloatN);

@ -100,6 +100,8 @@ public class CameraNode extends Node {
*/
@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

@ -44,6 +44,7 @@ import com.jme3.math.Matrix4f;
import com.jme3.renderer.Camera;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.IdentityCloneFunction;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.Queue;
@ -499,6 +500,13 @@ public class Geometry extends Spatial {
*/
@Override
public Geometry clone(boolean cloneMaterial) {
return (Geometry)super.clone(cloneMaterial);
}
/**
* The old clone() method that did not use the new Cloner utility.
*/
public Geometry oldClone(boolean cloneMaterial) {
Geometry geomClone = (Geometry) super.clone(cloneMaterial);
// This geometry is managed,
@ -542,6 +550,10 @@ public class Geometry extends Spatial {
*/
@Override
public Spatial deepClone() {
return super.deepClone();
}
public Spatial oldDeepClone() {
Geometry geomClone = clone(true);
geomClone.mesh = mesh.deepClone();
return geomClone;
@ -552,9 +564,42 @@ public class Geometry extends Spatial {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.mesh = cloner.clone(mesh);
super.cloneFields(cloner, original);
// If this is a grouped node and if our group node is
// also cloned then we'll grab it's reference.
if( groupNode != null ) {
if( cloner.isCloned(groupNode) ) {
// Then resolve the reference
this.groupNode = cloner.clone(groupNode);
} else {
// We are on our own now
this.groupNode = null;
this.startIndex = -1;
}
// The above is based on the fact that if we were
// cloning the hierarchy that contained the parent
// group then it would have been shallow cloned before
// this child. Can't really be otherwise.
}
this.cachedWorldMat = cloner.clone(cachedWorldMat);
// See if we are doing a shallow clone or a deep mesh clone
boolean shallowClone = (cloner.getCloneFunction(Mesh.class) instanceof IdentityCloneFunction);
// See if we clone the mesh using the special animation
// semi-deep cloning
if( shallowClone && mesh != null && mesh.getBuffer(Type.BindPosePosition) != null ) {
// Then we need to clone the mesh a little deeper
this.mesh = mesh.cloneForAnim();
} else {
// Do whatever the cloner wants to do about it
this.mesh = cloner.clone(mesh);
}
this.material = cloner.clone(material);
this.groupNode = cloner.clone(groupNode);
}
@Override

@ -100,6 +100,8 @@ public class LightNode extends Node {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// A change in behavior... I think previously LightNode was probably
// not really cloneable... or at least its lightControl would be pointing
// to the wrong control. -pspeed

@ -175,7 +175,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
private VertexBuffer[] lodLevels;
private float pointSize = 1;
private float lineWidth = -1;
private float lineWidth = 1;
private transient int vertexArrayID = -1;
@ -307,7 +307,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
@Override
public Mesh jmeClone() {
try {
return (Mesh)super.clone();
Mesh clone = (Mesh)super.clone();
clone.vertexArrayID = -1;
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
@ -632,6 +634,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
@Deprecated
public void setLineWidth(float lineWidth) {
if (lineWidth < 1f) {
throw new IllegalArgumentException("lineWidth must be greater than or equal to 1.0");
}
this.lineWidth = lineWidth;
}

@ -75,7 +75,6 @@ public class Node extends Spatial {
* requiresUpdate() method.
*/
private SafeArrayList<Spatial> updateList = null;
/**
* False if the update list requires rebuilding. This is Node.class
* specific and therefore not included as part of the Spatial update flags.
@ -100,7 +99,6 @@ public class Node extends Spatial {
*/
public Node(String name) {
super(name);
// For backwards compatibility, only clear the "requires
// update" flag if we are not a subclass of Node.
// This prevents subclass from silently failing to receive
@ -141,10 +139,21 @@ public class Node extends Spatial {
}
}
@Override
protected void setMatParamOverrideRefresh() {
super.setMatParamOverrideRefresh();
for (Spatial child : children.getArray()) {
if ((child.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
continue;
}
child.setMatParamOverrideRefresh();
}
}
@Override
protected void updateWorldBound(){
super.updateWorldBound();
// for a node, the world bound is a combination of all it's children
// bounds
BoundingVolume resultBound = null;
@ -239,19 +248,19 @@ public class Node extends Spatial {
// This branch has no geometric state that requires updates.
return;
}
if ((refreshFlags & RF_LIGHTLIST) != 0){
updateWorldLightList();
}
if ((refreshFlags & RF_TRANSFORM) != 0){
// combine with parent transforms- same for all spatial
// subclasses.
updateWorldTransforms();
}
if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
updateMatParamOverrides();
}
refreshFlags &= ~RF_CHILD_LIGHTLIST;
if (!children.isEmpty()) {
// the important part- make sure child geometric state is refreshed
// first before updating own world bound. This saves
@ -287,7 +296,6 @@ public class Node extends Spatial {
return count;
}
/**
* <code>getVertexCount</code> returns the number of vertices contained
* in all sub-branches of this node that contain geometry.
@ -321,7 +329,6 @@ public class Node extends Spatial {
public int attachChild(Spatial child) {
return attachChildAt(child, children.size());
}
/**
*
* <code>attachChildAt</code> attaches a child to this node at an index. This node
@ -345,20 +352,18 @@ public class Node extends Spatial {
}
child.setParent(this);
children.add(index, child);
// XXX: Not entirely correct? Forces bound update up the
// tree stemming from the attached child. Also forces
// transform update down the tree-
child.setTransformRefresh();
child.setLightListRefresh();
child.setMatParamOverrideRefresh();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
new Object[]{child.getName(), getName()});
}
invalidateUpdateList();
}
return children.size();
}
@ -433,6 +438,7 @@ public class Node extends Spatial {
child.setTransformRefresh();
// lights are also inherited from parent
child.setLightListRefresh();
child.setMatParamOverrideRefresh();
invalidateUpdateList();
}
@ -519,7 +525,6 @@ public class Node extends Spatial {
}
return null;
}
/**
* determines if the provided Spatial is contained in the children list of
* this node.
@ -567,39 +572,32 @@ public class Node extends Spatial {
public int collideWith(Collidable other, CollisionResults results){
int total = 0;
// optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
// number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
// The idea is when there are few children, it can be too expensive to test boundingVolume first.
/*
I'm removing this change until some issues can be addressed and I really
think it needs to be implemented a better way anyway.
First, it causes issues for anyone doing collideWith() with BoundingVolumes
and expecting it to trickle down to the children. For example, children
with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing
a collision check at the parent level then has to do a BoundingSphere to BoundingBox
collision which isn't resolved. (Having to come up with a collision point in that
case is tricky and the first sign that this is the wrong approach.)
Second, the rippling changes this caused to 'optimize' collideWith() for this
special use-case are another sign that this approach was a bit dodgy. The whole
idea of calculating a full collision just to see if the two shapes collide at all
is very wasteful.
A proper implementation should support a simpler boolean check that doesn't do
all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9%
of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much
faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
I don't have time to do it right now but I'll at least un-break a bunch of peoples'
code until it can be 'optimized' properly. Hopefully it's not too late to back out
the other dodgy ripples this caused. -pspeed (hindsight-expert ;))
Note: the code itself is relatively simple to implement but I don't have time to
a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast
enough to do all the time for > 1.
if (children.size() > 4)
{
BoundingVolume bv = this.getWorldBound();
@ -692,12 +690,21 @@ public class Node extends Spatial {
// Reset the fields of the clone that should be in a 'new' state.
nodeClone.updateList = null;
nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
return nodeClone;
}
@Override
public Spatial deepClone(){
public Spatial deepClone() {
Node nodeClone = (Node)super.deepClone();
// Reset the fields of the clone that should be in a 'new' state.
nodeClone.updateList = null;
nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
return nodeClone;
}
public Spatial oldDeepClone(){
Node nodeClone = (Node) super.clone();
nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
for (Spatial child : children){
@ -713,6 +720,8 @@ public class Node extends Spatial {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.children = cloner.clone(children);
// Only the outer cloning thing knows whether this should be nulled
@ -720,7 +729,6 @@ public class Node extends Spatial {
// cloning this list is fine.
this.updateList = cloner.clone(updateList);
}
@Override
public void write(JmeExporter e) throws IOException {
super.write(e);
@ -732,7 +740,6 @@ public class Node extends Spatial {
// XXX: Load children before loading itself!!
// This prevents empty children list if controls query
// it in Control.setSpatial().
children = new SafeArrayList( Spatial.class,
e.getCapsule(this).readSavableArrayList("children", null) );
@ -742,7 +749,6 @@ public class Node extends Spatial {
child.parent = this;
}
}
super.read(e);
}
@ -763,7 +769,6 @@ public class Node extends Spatial {
}
}
}
@Override
public void depthFirstTraversal(SceneGraphVisitor visitor) {
for (Spatial child : children.getArray()) {
@ -771,7 +776,6 @@ public class Node extends Spatial {
}
visitor.visit(this);
}
@Override
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
queue.addAll(children);

@ -38,6 +38,7 @@ import com.jme3.collision.Collidable;
import com.jme3.export.*;
import com.jme3.light.Light;
import com.jme3.light.LightList;
import com.jme3.material.MatParamOverride;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
@ -48,6 +49,7 @@ import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.IdentityCloneFunction;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
@ -121,7 +123,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
RF_BOUND = 0x02,
RF_LIGHTLIST = 0x04, // changes in light lists
RF_CHILD_LIGHTLIST = 0x08; // some child need geometry update
RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update
RF_MATPARAM_OVERRIDE = 0x10;
protected CullHint cullHint = CullHint.Inherit;
protected BatchHint batchHint = BatchHint.Inherit;
@ -134,6 +137,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
protected LightList localLights;
protected transient LightList worldLights;
protected ArrayList<MatParamOverride> localOverrides;
protected ArrayList<MatParamOverride> worldOverrides;
/**
* This spatial's name.
*/
@ -194,13 +201,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
protected Spatial(String name) {
this.name = name;
localTransform = new Transform();
worldTransform = new Transform();
localLights = new LightList(this);
worldLights = new LightList(this);
localOverrides = new ArrayList<>();
worldOverrides = new ArrayList<>();
refreshFlags |= RF_BOUND;
}
@ -221,7 +229,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
boolean requiresUpdates() {
return requiresUpdates | !controls.isEmpty();
}
/**
* Subclasses can call this with true to denote that they require
* updateLogicalState() to be called even if they contain no controls.
@ -271,35 +278,33 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected void setLightListRefresh() {
refreshFlags |= RF_LIGHTLIST;
// Make sure next updateGeometricState() visits this branch
// to update lights.
Spatial p = parent;
while (p != null) {
//if (p.refreshFlags != 0) {
// any refresh flag is sufficient,
// as each propagates to the root Node
// 2015/2/8:
// This is not true, because using e.g. getWorldBound()
// or getWorldTransform() activates a "partial refresh"
// which does not update the lights but does clear
// the refresh flags on the ancestors!
// return;
//}
if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
// The parent already has this flag,
// so must all ancestors.
return;
}
p.refreshFlags |= RF_CHILD_LIGHTLIST;
p = p.parent;
}
}
protected void setMatParamOverrideRefresh() {
refreshFlags |= RF_MATPARAM_OVERRIDE;
Spatial p = parent;
while (p != null) {
if ((p.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
return;
}
p.refreshFlags |= RF_MATPARAM_OVERRIDE;
p = p.parent;
}
}
/**
* Indicate that the bounding of this spatial has changed and that
* a refresh is required.
@ -317,7 +322,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
p = p.parent;
}
}
/**
* (Internal use only) Forces a refresh of the given types of data.
*
@ -423,6 +427,29 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
return worldLights;
}
/**
* Get the local material parameter overrides.
*
* @return The list of local material parameter overrides.
*/
public List<MatParamOverride> getLocalMatParamOverrides() {
return localOverrides;
}
/**
* Get the world material parameter overrides.
*
* Note that this list is only updated on a call to
* {@link #updateGeometricState()}. After update, the world overrides list
* will contain the {@link #getParent() parent's} world overrides combined
* with this spatial's {@link #getLocalMatParamOverrides() local overrides}.
*
* @return The list of world material parameter overrides.
*/
public List<MatParamOverride> getWorldMatParamOverrides() {
return worldOverrides;
}
/**
* <code>getWorldRotation</code> retrieves the absolute rotation of the
* Spatial.
@ -524,10 +551,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
TempVars vars = TempVars.get();
Vector3f compVecA = vars.vect4;
compVecA.set(position).subtractLocal(worldTranslation);
getLocalRotation().lookAt(compVecA, upVector);
if ( getParent() != null ) {
Quaternion rot=vars.quat1;
rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
@ -554,15 +579,63 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
worldLights.update(localLights, null);
refreshFlags &= ~RF_LIGHTLIST;
} else {
if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
worldLights.update(localLights, parent.worldLights);
refreshFlags &= ~RF_LIGHTLIST;
} else {
assert false;
}
assert (parent.refreshFlags & RF_LIGHTLIST) == 0;
worldLights.update(localLights, parent.worldLights);
refreshFlags &= ~RF_LIGHTLIST;
}
}
protected void updateMatParamOverrides() {
refreshFlags &= ~RF_MATPARAM_OVERRIDE;
worldOverrides.clear();
if (parent == null) {
worldOverrides.addAll(localOverrides);
} else {
assert (parent.refreshFlags & RF_MATPARAM_OVERRIDE) == 0;
worldOverrides.addAll(localOverrides);
worldOverrides.addAll(parent.worldOverrides);
}
}
/**
* Adds a local material parameter override.
*
* @param override The override to add.
* @see MatParamOverride
*/
public void addMatParamOverride(MatParamOverride override) {
if (override == null) {
throw new IllegalArgumentException("override cannot be null");
}
localOverrides.add(override);
setMatParamOverrideRefresh();
}
/**
* Remove a local material parameter override if it exists.
*
* @param override The override to remove.
* @see MatParamOverride
*/
public void removeMatParamOverride(MatParamOverride override) {
if (localOverrides.remove(override)) {
setMatParamOverrideRefresh();
}
}
/**
* Remove all local material parameter overrides.
*
* @see #addMatParamOverride(com.jme3.material.MatParamOverride)
*/
public void clearMatParamOverrides() {
if (!localOverrides.isEmpty()) {
setMatParamOverrideRefresh();
}
localOverrides.clear();
}
/**
* Should only be called from updateGeometricState().
* In most cases should not be subclassed.
@ -695,7 +768,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
controls.add(control);
control.setSpatial(this);
boolean after = requiresUpdates();
// If the requirement to be updated has changed
// then we need to let the parent node know so it
// can rebuild its update list.
@ -719,7 +791,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
}
}
boolean after = requiresUpdates();
// If the requirement to be updated has changed
// then we need to let the parent node know so it
// can rebuild its update list.
@ -745,14 +816,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
}
boolean after = requiresUpdates();
// If the requirement to be updated has changed
// then we need to let the parent node know so it
// can rebuild its update list.
if( parent != null && before != after ) {
parent.invalidateUpdateList();
}
return result;
}
@ -837,6 +906,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
if ((refreshFlags & RF_BOUND) != 0) {
updateWorldBound();
}
if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
updateMatParamOverrides();
}
assert refreshFlags == 0;
}
@ -1263,12 +1335,43 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Note that meshes of geometries are not cloned explicitly, they
* are shared if static, or specially cloned if animated.
*
* All controls will be cloned using the Control.cloneForSpatial method
* on the clone.
*
* @see Mesh#cloneForAnim()
*/
public Spatial clone(boolean cloneMaterial) {
public Spatial clone( boolean cloneMaterial ) {
// Setup the cloner for the type of cloning we want to do.
Cloner cloner = new Cloner();
// First, we definitely do not want to clone our own parent
cloner.setClonedValue(parent, null);
// If we aren't cloning materials then we will make sure those
// aren't cloned also
if( !cloneMaterial ) {
cloner.setCloneFunction(Material.class, new IdentityCloneFunction<Material>());
}
// By default the meshes are not cloned. The geometry
// may choose to selectively force them to be cloned but
// normally they will be shared
cloner.setCloneFunction(Mesh.class, new IdentityCloneFunction<Mesh>());
// Clone it!
Spatial clone = cloner.clone(this);
// Because we've nulled the parent out we need to make sure
// the transforms and stuff get refreshed.
clone.setTransformRefresh();
clone.setLightListRefresh();
clone.setMatParamOverrideRefresh();
return clone;
}
/**
* The old clone() method that did not use the new Cloner utility.
*/
public Spatial oldClone(boolean cloneMaterial) {
try {
Spatial clone = (Spatial) super.clone();
if (worldBound != null) {
@ -1281,6 +1384,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
clone.localLights.setOwner(clone);
clone.worldLights.setOwner(clone);
clone.worldOverrides = new ArrayList<MatParamOverride>();
clone.localOverrides = new ArrayList<MatParamOverride>();
for (MatParamOverride override : localOverrides) {
clone.localOverrides.add((MatParamOverride) override.clone());
}
// No need to force cloned to update.
// This node already has the refresh flags
// set below so it will have to update anyway.
@ -1302,6 +1412,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
clone.setBoundRefresh();
clone.setTransformRefresh();
clone.setLightListRefresh();
clone.setMatParamOverrideRefresh();
clone.controls = new SafeArrayList<Control>(Control.class);
for (int i = 0; i < controls.size(); i++) {
@ -1344,7 +1455,23 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*
* @see Spatial#clone()
*/
public abstract Spatial deepClone();
public Spatial deepClone() {
// Setup the cloner for the type of cloning we want to do.
Cloner cloner = new Cloner();
// First, we definitely do not want to clone our own parent
cloner.setClonedValue(parent, null);
Spatial clone = cloner.clone(this);
// Because we've nulled the parent out we need to make sure
// the transforms and stuff get refreshed.
clone.setTransformRefresh();
clone.setLightListRefresh();
clone.setMatParamOverrideRefresh();
return clone;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
@ -1373,6 +1500,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
this.localLights = cloner.clone(localLights);
this.worldTransform = cloner.clone(worldTransform);
this.localTransform = cloner.clone(localTransform);
this.worldOverrides = cloner.clone(worldOverrides);
this.localOverrides = cloner.clone(localOverrides);
this.controls = cloner.clone(controls);
// Cloner doesn't handle maps on its own just yet.
@ -1381,13 +1510,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
// to avoid all of the nasty cloneForSpatial() fixup style code that
// used to inject stuff into the clone's user data. By using cloner
// to clone the user data we get this automatically.
userData = (HashMap<String, Savable>)userData.clone();
for( Map.Entry<String, Savable> e : userData.entrySet() ) {
Savable value = e.getValue();
if( value instanceof Cloneable ) {
// Note: all JmeCloneable objects are also Cloneable so this
// catches both cases.
e.setValue(cloner.clone(value));
if( userData != null ) {
userData = (HashMap<String, Savable>)userData.clone();
for( Map.Entry<String, Savable> e : userData.entrySet() ) {
Savable value = e.getValue();
if( value instanceof Cloneable ) {
// Note: all JmeCloneable objects are also Cloneable so this
// catches both cases.
e.setValue(cloner.clone(value));
}
}
}
}
@ -1467,6 +1598,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit);
capsule.write(localTransform, "transform", Transform.IDENTITY);
capsule.write(localLights, "lights", null);
capsule.writeSavableArrayList(localOverrides, "overrides", null);
// Shallow clone the controls array to convert its type.
capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null);
@ -1490,6 +1622,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
localLights = (LightList) ic.readSavable("lights", null);
localLights.setOwner(this);
localOverrides = ic.readSavableArrayList("overrides", null);
if (localOverrides == null) {
localOverrides = new ArrayList<>();
}
worldOverrides = new ArrayList<>();
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
//the AnimControl creates the SkeletonControl for old files and add it to the spatial.
//The SkeletonControl must be the last in the stack so we add the list of all other control before it.

@ -522,6 +522,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
// throw new UnsupportedOperationException("Data has already been sent. Cannot set usage.");
this.usage = usage;
this.setUpdateNeeded();
}
/**

@ -349,6 +349,8 @@ public class InstancedGeometry extends Geometry {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.globalInstanceData = cloner.clone(globalInstanceData);
this.transformInstanceData = cloner.clone(transformInstanceData);
this.geometries = cloner.clone(geometries);

@ -350,6 +350,8 @@ public class InstancedNode extends GeometryGroupNode {
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.control = cloner.clone(control);
this.lookUp = cloner.clone(lookUp);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,256 +31,149 @@
*/
package com.jme3.shader;
import com.jme3.export.*;
import com.jme3.material.MatParam;
import com.jme3.material.TechniqueDef;
import com.jme3.util.ListMap;
import java.util.Arrays;
import java.util.List;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
public final class DefineList implements Savable, Cloneable {
private static final String ONE = "1";
private TreeMap<String, String> defines = new TreeMap<String, String>();
private String compiled = null;
private int cachedHashCode = 0;
/**
* The new define list.
*
* @author Kirill Vainer
*/
public final class DefineList {
public void write(JmeExporter ex) throws IOException{
OutputCapsule oc = ex.getCapsule(this);
public static final int MAX_DEFINES = 64;
String[] keys = new String[defines.size()];
String[] vals = new String[defines.size()];
private long hash;
private final int[] vals;
int i = 0;
for (Map.Entry<String, String> define : defines.entrySet()){
keys[i] = define.getKey();
vals[i] = define.getValue();
i++;
public DefineList(int numValues) {
if (numValues < 0 || numValues > MAX_DEFINES) {
throw new IllegalArgumentException("numValues must be between 0 and 64");
}
oc.write(keys, "keys", null);
oc.write(vals, "vals", null);
vals = new int[numValues];
}
public void read(JmeImporter im) throws IOException{
InputCapsule ic = im.getCapsule(this);
String[] keys = ic.readStringArray("keys", null);
String[] vals = ic.readStringArray("vals", null);
for (int i = 0; i < keys.length; i++){
defines.put(keys[i], vals[i]);
}
private DefineList(DefineList original) {
this.hash = original.hash;
this.vals = new int[original.vals.length];
System.arraycopy(original.vals, 0, vals, 0, vals.length);
}
public void clear() {
defines.clear();
compiled = "";
cachedHashCode = 0;
public void set(int id, int val) {
assert 0 <= id && id < 64;
if (val != 0) {
hash |= (1L << id);
} else {
hash &= ~(1L << id);
}
vals[id] = val;
}
public String get(String key){
return defines.get(key);
public void set(int id, float val) {
set(id, Float.floatToIntBits(val));
}
@Override
public DefineList clone() {
try {
DefineList clone = (DefineList) super.clone();
clone.cachedHashCode = 0;
clone.compiled = null;
clone.defines = (TreeMap<String, String>) defines.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
public void set(int id, boolean val) {
set(id, val ? 1 : 0);
}
public boolean set(String key, VarType type, Object val){
if (val == null){
defines.remove(key);
compiled = null;
cachedHashCode = 0;
return true;
public void set(int id, VarType type, Object value) {
if (value == null) {
set(id, 0);
return;
}
switch (type){
case Boolean:
if (((Boolean) val).booleanValue()) {
// same literal, != will work
if (defines.put(key, ONE) != ONE) {
compiled = null;
cachedHashCode = 0;
return true;
}
} else if (defines.containsKey(key)) {
defines.remove(key);
compiled = null;
cachedHashCode = 0;
return true;
}
switch (type) {
case Int:
set(id, (Integer) value);
break;
case Float:
case Int:
String newValue = val.toString();
String original = defines.put(key, newValue);
if (!val.equals(original)) {
compiled = null;
cachedHashCode = 0;
return true;
}
set(id, (Float) value);
break;
case Boolean:
set(id, ((Boolean) value));
break;
default:
// same literal, != will work
if (defines.put(key, ONE) != ONE) {
compiled = null;
cachedHashCode = 0;
return true;
}
set(id, 1);
break;
}
return false;
}
public boolean remove(String key){
if (defines.remove(key) != null) {
compiled = null;
cachedHashCode = 0;
return true;
public void setAll(DefineList other) {
for (int i = 0; i < other.vals.length; i++) {
if (other.vals[i] != 0) {
vals[i] = other.vals[i];
}
}
return false;
}
public void addFrom(DefineList other){
if (other == null) {
return;
}
compiled = null;
cachedHashCode = 0;
defines.putAll(other.defines);
public void clear() {
hash = 0;
Arrays.fill(vals, 0);
}
public String getCompiled(){
if (compiled == null){
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : defines.entrySet()){
sb.append("#define ").append(entry.getKey()).append(" ");
sb.append(entry.getValue()).append('\n');
}
compiled = sb.toString();
}
return compiled;
public boolean getBoolean(int id) {
return vals[id] != 0;
}
@Override
public boolean equals(Object obj) {
final DefineList other = (DefineList) obj;
return defines.equals(other.defines);
public float getFloat(int id) {
return Float.intBitsToFloat(vals[id]);
}
/**
* Update defines if the define list changed based on material parameters.
* @param params
* @param def
* @return true if defines was updated
*/
public boolean update(ListMap params, TechniqueDef def){
if(equalsParams(params, def)){
return false;
}
// Defines were changed, update define list
clear();
for(int i=0;i<params.size();i++) {
MatParam param = (MatParam)params.getValue(i);
String defineName = def.getShaderParamDefine(param.getName());
if (defineName != null) {
set(defineName, param.getVarType(), param.getValue());
}
}
return true;
public int getInt(int id) {
return vals[id];
}
private boolean equalsParams(ListMap params, TechniqueDef def) {
@Override
public int hashCode() {
return (int)((hash >> 32) ^ hash);
}
int size = 0;
@Override
public boolean equals(Object other) {
DefineList o = (DefineList) other;
if (hash == o.hash) {
for (int i = 0; i < vals.length; i++) {
if (vals[i] != o.vals[i]) return false;
}
return true;
}
return false;
}
for(int i = 0; i < params.size() ; i++ ) {
MatParam param = (MatParam)params.getValue(i);
String key = def.getShaderParamDefine(param.getName());
if (key != null) {
Object val = param.getValue();
if (val != null) {
public DefineList deepClone() {
return new DefineList(this);
}
switch (param.getVarType()) {
case Boolean: {
String current = defines.get(key);
if (((Boolean) val).booleanValue()) {
if (current == null || current != ONE) {
return false;
}
size++;
} else {
if (current != null) {
return false;
}
}
}
break;
case Float:
case Int: {
String newValue = val.toString();
String current = defines.get(key);
if (!newValue.equals(current)) {
return false;
}
size++;
}
break;
default: {
if (!defines.containsKey(key)) {
return false;
}
size++;
public void generateSource(StringBuilder sb, List<String> defineNames, List<VarType> defineTypes) {
for (int i = 0; i < vals.length; i++) {
if (vals[i] != 0) {
String defineName = defineNames.get(i);
sb.append("#define ");
sb.append(defineName);
sb.append(" ");
if (defineTypes != null && defineTypes.get(i) == VarType.Float) {
float val = Float.intBitsToFloat(vals[i]);
if (Float.isInfinite(val) || Float.isNaN(val)) {
throw new IllegalArgumentException(
"GLSL does not support NaN "
+ "or Infinite float literals");
}
break;
}
sb.append(val);
} else {
sb.append(vals[i]);
}
sb.append("\n");
}
}
if (size != defines.size()) {
return false;
}
return true;
}
@Override
public int hashCode() {
if (cachedHashCode == 0) {
cachedHashCode = defines.hashCode();
}
return cachedHashCode;
}
@Override
public String toString(){
public String generateSource(List<String> defineNames, List<VarType> defineTypes) {
StringBuilder sb = new StringBuilder();
int i = 0;
for (Map.Entry<String, String> entry : defines.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue());
if (i != defines.size() - 1) {
sb.append(", ");
}
i++;
}
generateSource(sb, defineNames, defineTypes);
return sb.toString();
}
}

@ -258,7 +258,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
}
for (ShaderNodeVariable var : shaderNode.getDefinition().getOutputs()) {
ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName());
ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName(), var.getMultiplicity());
if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) {
if (!isVarying(info, v)) {
declareVariable(source, v);
@ -397,6 +397,11 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
source.append(mapping.getLeftVariable().getNameSpace());
source.append("_");
source.append(mapping.getLeftVariable().getName());
if (mapping.getLeftVariable().getMultiplicity() != null){
source.append("[");
source.append(mapping.getLeftVariable().getMultiplicity());
source.append("]");
}
//left swizzle, the variable can't be declared and assigned on the same line.
if (mapping.getLeftSwizzling().length() > 0) {

@ -45,44 +45,63 @@ public final class Shader extends NativeObject {
/**
* A list of all shader sources currently attached.
*/
private ArrayList<ShaderSource> shaderSourceList;
private final ArrayList<ShaderSource> shaderSourceList;
/**
* Maps uniform name to the uniform variable.
*/
private ListMap<String, Uniform> uniforms;
private final ListMap<String, Uniform> uniforms;
/**
* Uniforms bound to {@link UniformBinding}s.
*
* Managed by the {@link UniformBindingManager}.
*/
private final ArrayList<Uniform> boundUniforms;
/**
* Maps attribute name to the location of the attribute in the shader.
*/
private IntMap<Attribute> attribs;
private final IntMap<Attribute> attribs;
/**
* Type of shader. The shader will control the pipeline of it's type.
*/
public static enum ShaderType {
/**
* Control fragment rasterization. (e.g color of pixel).
*/
Fragment,
Fragment("frag"),
/**
* Control vertex processing. (e.g transform of model to clip space)
*/
Vertex,
Vertex("vert"),
/**
* Control geometry assembly. (e.g compile a triangle list from input data)
* Control geometry assembly. (e.g compile a triangle list from input
* data)
*/
Geometry,
Geometry("geom"),
/**
* Controls tesselation factor (e.g how often a input patch should be subdivided)
* Controls tesselation factor (e.g how often a input patch should be
* subdivided)
*/
TessellationControl,
TessellationControl("tsctrl"),
/**
* Controls tesselation transform (e.g similar to the vertex shader, but required to mix inputs manual)
* Controls tesselation transform (e.g similar to the vertex shader, but
* required to mix inputs manual)
*/
TessellationEvaluation;
TessellationEvaluation("tseval");
private String extension;
public String getExtension() {
return extension;
}
private ShaderType(String extension) {
this.extension = extension;
}
}
/**
@ -195,22 +214,16 @@ public final class Shader extends NativeObject {
}
}
/**
* Initializes the shader for use, must be called after the
* constructor without arguments is used.
*/
public void initialize() {
shaderSourceList = new ArrayList<ShaderSource>();
uniforms = new ListMap<String, Uniform>();
attribs = new IntMap<Attribute>();
}
/**
* Creates a new shader, {@link #initialize() } must be called
* after this constructor for the shader to be usable.
*/
public Shader(){
super();
shaderSourceList = new ArrayList<ShaderSource>();
uniforms = new ListMap<String, Uniform>();
attribs = new IntMap<Attribute>();
boundUniforms = new ArrayList<Uniform>();
}
/**
@ -225,6 +238,10 @@ public final class Shader extends NativeObject {
for (ShaderSource source : s.shaderSourceList){
shaderSourceList.add( (ShaderSource)source.createDestructableClone() );
}
uniforms = null;
boundUniforms = null;
attribs = null;
}
/**
@ -248,6 +265,18 @@ public final class Shader extends NativeObject {
setUpdateNeeded();
}
public void addUniformBinding(UniformBinding binding){
String uniformName = "g_" + binding.name();
Uniform uniform = uniforms.get(uniformName);
if (uniform == null) {
uniform = new Uniform();
uniform.name = uniformName;
uniform.binding = binding;
uniforms.put(uniformName, uniform);
boundUniforms.add(uniform);
}
}
public Uniform getUniform(String name){
assert name.startsWith("m_") || name.startsWith("g_");
Uniform uniform = uniforms.get(name);
@ -278,6 +307,10 @@ public final class Shader extends NativeObject {
return uniforms;
}
public ArrayList<Uniform> getBoundUniforms() {
return boundUniforms;
}
public Collection<ShaderSource> getSources(){
return shaderSourceList;
}

@ -57,9 +57,9 @@ public abstract class ShaderGenerator {
*/
protected int indent;
/**
* the technique to use for the shader generation
* the technique def to use for the shader generation
*/
protected Technique technique = null;
protected TechniqueDef techniqueDef = null;
/**
* Build a shaderGenerator
@ -70,8 +70,8 @@ public abstract class ShaderGenerator {
this.assetManager = assetManager;
}
public void initialize(Technique technique){
this.technique = technique;
public void initialize(TechniqueDef techniqueDef){
this.techniqueDef = techniqueDef;
}
/**
@ -79,24 +79,29 @@ public abstract class ShaderGenerator {
*
* @return a Shader program
*/
public Shader generateShader() {
if(technique == null){
throw new UnsupportedOperationException("The shaderGenerator was not properly initialized, call initialize(Technique) before any generation");
public Shader generateShader(String definesSourceCode) {
if (techniqueDef == null) {
throw new UnsupportedOperationException("The shaderGenerator was not "
+ "properly initialized, call "
+ "initialize(TechniqueDef) before any generation");
}
DefineList defines = technique.getAllDefines();
TechniqueDef def = technique.getDef();
ShaderGenerationInfo info = def.getShaderGenerationInfo();
String vertexSource = buildShader(def.getShaderNodes(), info, ShaderType.Vertex);
String fragmentSource = buildShader(def.getShaderNodes(), info, ShaderType.Fragment);
String techniqueName = techniqueDef.getName();
ShaderGenerationInfo info = techniqueDef.getShaderGenerationInfo();
Shader shader = new Shader();
shader.initialize();
shader.addSource(Shader.ShaderType.Vertex, technique.getDef().getName() + ".vert", vertexSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Vertex));
shader.addSource(Shader.ShaderType.Fragment, technique.getDef().getName() + ".frag", fragmentSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Fragment));
for (ShaderType type : ShaderType.values()) {
String extension = type.getExtension();
String language = getLanguageAndVersion(type);
String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
if (shaderSourceCode != null) {
String shaderSourceAssetName = techniqueName + "." + extension;
shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
}
}
technique = null;
techniqueDef = null;
return shader;
}
@ -109,6 +114,14 @@ public abstract class ShaderGenerator {
* @return the code of the generated vertex shader
*/
protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
if (type == ShaderType.TessellationControl ||
type == ShaderType.TessellationEvaluation ||
type == ShaderType.Geometry) {
// TODO: Those are not supported.
// Too much code assumes that type is either Vertex or Fragment
return null;
}
indent = 0;
StringBuilder sourceDeclaration = new StringBuilder();

@ -1,201 +0,0 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.shader;
import com.jme3.asset.AssetKey;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Set;
public class ShaderKey extends AssetKey<Shader> {
protected EnumMap<Shader.ShaderType,String> shaderLanguage;
protected EnumMap<Shader.ShaderType,String> shaderName;
protected DefineList defines;
protected int cachedHashedCode = 0;
protected boolean usesShaderNodes = false;
public ShaderKey(){
shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
}
public ShaderKey(DefineList defines, EnumMap<Shader.ShaderType,String> shaderLanguage,EnumMap<Shader.ShaderType,String> shaderName){
super("");
this.name = reducePath(getShaderName(Shader.ShaderType.Vertex));
this.shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
this.shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
this.defines = defines;
for (Shader.ShaderType shaderType : shaderName.keySet()) {
this.shaderName.put(shaderType,shaderName.get(shaderType));
this.shaderLanguage.put(shaderType,shaderLanguage.get(shaderType));
}
}
@Override
public ShaderKey clone() {
ShaderKey clone = (ShaderKey) super.clone();
clone.cachedHashedCode = 0;
clone.defines = defines.clone();
return clone;
}
@Override
public String toString(){
//todo:
return "V="+name+";";
}
private final String getShaderName(Shader.ShaderType type) {
if (shaderName == null) {
return "";
}
String shName = shaderName.get(type);
return shName != null ? shName : "";
}
//todo: make equals and hashCode work
@Override
public boolean equals(Object obj) {
final ShaderKey other = (ShaderKey) obj;
if (name.equals(other.name) && getShaderName(Shader.ShaderType.Fragment).equals(other.getShaderName(Shader.ShaderType.Fragment))){
if (defines != null && other.defines != null) {
return defines.equals(other.defines);
} else if (defines != null || other.defines != null) {
return false;
} else {
return true;
}
}
return false;
}
@Override
public int hashCode() {
if (cachedHashedCode == 0) {
int hash = 7;
hash = 41 * hash + name.hashCode();
hash = 41 * hash + getShaderName(Shader.ShaderType.Fragment).hashCode();
hash = getShaderName(Shader.ShaderType.Geometry) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.Geometry).hashCode();
hash = getShaderName(Shader.ShaderType.TessellationControl) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationControl).hashCode();
hash = getShaderName(Shader.ShaderType.TessellationEvaluation) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationEvaluation).hashCode();
hash = 41 * hash + (defines != null ? defines.hashCode() : 0);
cachedHashedCode = hash;
}
return cachedHashedCode;
}
public DefineList getDefines() {
return defines;
}
public String getVertName(){
return getShaderName(Shader.ShaderType.Vertex);
}
public String getFragName() {
return getShaderName(Shader.ShaderType.Fragment);
}
/**
* @deprecated Use {@link #getVertexShaderLanguage() } instead.
*/
@Deprecated
public String getLanguage() {
return shaderLanguage.get(Shader.ShaderType.Vertex);
}
public String getVertexShaderLanguage() {
return shaderLanguage.get(Shader.ShaderType.Vertex);
}
public String getFragmentShaderLanguage() {
return shaderLanguage.get(Shader.ShaderType.Vertex);
}
public boolean isUsesShaderNodes() {
return usesShaderNodes;
}
public void setUsesShaderNodes(boolean usesShaderNodes) {
this.usesShaderNodes = usesShaderNodes;
}
public Set<Shader.ShaderType> getUsedShaderPrograms(){
return shaderName.keySet();
}
public String getShaderProgramLanguage(Shader.ShaderType shaderType){
return shaderLanguage.get(shaderType);
}
public String getShaderProgramName(Shader.ShaderType shaderType){
return getShaderName(shaderType);
}
@Override
public void write(JmeExporter ex) throws IOException{
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(shaderName.get(Shader.ShaderType.Fragment), "fragment_name", null);
oc.write(shaderName.get(Shader.ShaderType.Geometry), "geometry_name", null);
oc.write(shaderName.get(Shader.ShaderType.TessellationControl), "tessControl_name", null);
oc.write(shaderName.get(Shader.ShaderType.TessellationEvaluation), "tessEval_name", null);
oc.write(shaderLanguage.get(Shader.ShaderType.Vertex), "language", null);
oc.write(shaderLanguage.get(Shader.ShaderType.Fragment), "frag_language", null);
oc.write(shaderLanguage.get(Shader.ShaderType.Geometry), "geom_language", null);
oc.write(shaderLanguage.get(Shader.ShaderType.TessellationControl), "tsctrl_language", null);
oc.write(shaderLanguage.get(Shader.ShaderType.TessellationEvaluation), "tseval_language", null);
}
@Override
public void read(JmeImporter im) throws IOException{
super.read(im);
InputCapsule ic = im.getCapsule(this);
shaderName.put(Shader.ShaderType.Vertex,name);
shaderName.put(Shader.ShaderType.Fragment,ic.readString("fragment_name", null));
shaderName.put(Shader.ShaderType.Geometry,ic.readString("geometry_name", null));
shaderName.put(Shader.ShaderType.TessellationControl,ic.readString("tessControl_name", null));
shaderName.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tessEval_name", null));
shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("language", null));
shaderLanguage.put(Shader.ShaderType.Fragment,ic.readString("frag_language", null));
shaderLanguage.put(Shader.ShaderType.Geometry,ic.readString("geom_language", null));
shaderLanguage.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrl_language", null));
shaderLanguage.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tseval_language", null));
}
}

@ -70,6 +70,30 @@ public class Uniform extends ShaderVariable {
*/
protected boolean setByCurrentMaterial = false;
@Override
public int hashCode() {
int hash = 5;
hash = 31 * hash + (this.value != null ? this.value.hashCode() : 0);
hash = 31 * hash + (this.varType != null ? this.varType.hashCode() : 0);
hash = 31 * hash + (this.binding != null ? this.binding.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
final Uniform other = (Uniform) obj;
if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
return false;
}
return this.binding == other.binding && this.varType == other.varType;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
@ -103,6 +127,10 @@ public class Uniform extends ShaderVariable {
return value;
}
public FloatBuffer getMultiData() {
return multiData;
}
public boolean isSetByCurrentMaterial() {
return setByCurrentMaterial;
}
@ -111,21 +139,6 @@ public class Uniform extends ShaderVariable {
setByCurrentMaterial = false;
}
private static void setVector4(Vector4f vec, Object value) {
if (value instanceof ColorRGBA) {
ColorRGBA color = (ColorRGBA) value;
vec.set(color.r, color.g, color.b, color.a);
} else if (value instanceof Quaternion) {
Quaternion quat = (Quaternion) value;
vec.set(quat.getX(), quat.getY(), quat.getZ(), quat.getW());
} else if (value instanceof Vector4f) {
Vector4f vec4 = (Vector4f) value;
vec.set(vec4);
} else{
throw new IllegalArgumentException();
}
}
public void clearValue(){
updateNeeded = true;
@ -182,27 +195,43 @@ public class Uniform extends ShaderVariable {
}
if (value == null) {
throw new NullPointerException();
throw new IllegalArgumentException("for uniform " + name + ": value cannot be null");
}
setByCurrentMaterial = true;
switch (type){
case Matrix3:
if (value.equals(this.value)) {
return;
}
Matrix3f m3 = (Matrix3f) value;
if (multiData == null) {
multiData = BufferUtils.createFloatBuffer(9);
}
m3.fillFloatBuffer(multiData, true);
multiData.clear();
if (this.value == null) {
this.value = new Matrix3f(m3);
} else {
((Matrix3f)this.value).set(m3);
}
break;
case Matrix4:
if (value.equals(this.value)) {
return;
}
Matrix4f m4 = (Matrix4f) value;
if (multiData == null) {
multiData = BufferUtils.createFloatBuffer(16);
}
m4.fillFloatBuffer(multiData, true);
multiData.clear();
if (this.value == null) {
this.value = new Matrix4f(m4);
} else {
((Matrix4f)this.value).copy(m4);
}
break;
case IntArray:
int[] ia = (int[]) value;
@ -283,11 +312,32 @@ public class Uniform extends ShaderVariable {
}
multiData.clear();
break;
case Vector4:
if (value.equals(this.value)) {
return;
}
if (value instanceof ColorRGBA) {
if (this.value == null) {
this.value = new ColorRGBA();
}
((ColorRGBA) this.value).set((ColorRGBA) value);
} else if (value instanceof Vector4f) {
if (this.value == null) {
this.value = new Vector4f();
}
((Vector4f) this.value).set((Vector4f) value);
} else {
if (this.value == null) {
this.value = new Quaternion();
}
((Quaternion) this.value).set((Quaternion) value);
}
break;
// Only use check if equals optimization for primitive values
case Int:
case Float:
case Boolean:
if (this.value != null && this.value.equals(value)) {
if (value.equals(this.value)) {
return;
}
this.value = value;
@ -297,39 +347,38 @@ public class Uniform extends ShaderVariable {
break;
}
if (multiData != null) {
this.value = multiData;
}
// if (multiData != null) {
// this.value = multiData;
// }
varType = type;
updateNeeded = true;
}
public void setVector4Length(int length){
if (location == -1)
if (location == -1) {
return;
FloatBuffer fb = (FloatBuffer) value;
if (fb == null || fb.capacity() < length * 4) {
value = BufferUtils.createFloatBuffer(length * 4);
}
multiData = BufferUtils.ensureLargeEnough(multiData, length * 4);
value = multiData;
varType = VarType.Vector4Array;
updateNeeded = true;
setByCurrentMaterial = true;
}
public void setVector4InArray(float x, float y, float z, float w, int index){
if (location == -1)
if (location == -1) {
return;
}
if (varType != null && varType != VarType.Vector4Array)
throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
if (varType != null && varType != VarType.Vector4Array) {
throw new IllegalArgumentException("Expected a " + varType.name() + " value!");
}
FloatBuffer fb = (FloatBuffer) value;
fb.position(index * 4);
fb.put(x).put(y).put(z).put(w);
fb.rewind();
multiData.position(index * 4);
multiData.put(x).put(y).put(z).put(w);
multiData.rewind();
updateNeeded = true;
setByCurrentMaterial = true;
}

@ -36,7 +36,7 @@ import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.system.Timer;
import java.util.List;
import java.util.ArrayList;
/**
* <code>UniformBindingManager</code> helps {@link RenderManager} to manage
@ -84,7 +84,8 @@ public class UniformBindingManager {
* Updates the given list of uniforms with {@link UniformBinding uniform bindings}
* based on the current world state.
*/
public void updateUniformBindings(List<Uniform> params) {
public void updateUniformBindings(Shader shader) {
ArrayList<Uniform> params = shader.getBoundUniforms();
for (int i = 0; i < params.size(); i++) {
Uniform u = params.get(i);
switch (u.getBinding()) {

@ -55,7 +55,7 @@ public enum VarType {
TextureBuffer(false,true,"sampler1D|sampler1DShadow"),
Texture2D(false,true,"sampler2D|sampler2DShadow"),
Texture3D(false,true,"sampler3D"),
TextureArray(false,true,"sampler2DArray"),
TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow"),
TextureCubeMap(false,true,"samplerCube"),
Int("int");

@ -128,12 +128,13 @@ public class NullContext implements JmeContext, Runnable {
public void run(){
initInThread();
while (!needClose.get()){
do {
listener.update();
if (frameRate > 0)
if (frameRate > 0) {
sync(frameRate);
}
}
} while (!needClose.get());
deinitInThread();

@ -51,7 +51,7 @@ import com.jme3.texture.Texture;
public class NullRenderer implements Renderer {
private static final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
private static final EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
private static final Statistics stats = new Statistics();
public void initialize() {

@ -97,6 +97,7 @@ public class FrameBuffer extends NativeObject {
int id = -1;
int slot = SLOT_UNDEF;
int face = -1;
int layer = -1;
/**
* @return The image format of the render buffer.
@ -160,6 +161,10 @@ public class FrameBuffer extends NativeObject {
return "BufferTarget[format=" + format + "]";
}
}
public int getLayer() {
return this.layer;
}
}
/**
@ -324,6 +329,19 @@ public class FrameBuffer extends NativeObject {
addColorTexture(tex);
}
/**
* Set the color texture array to use for this framebuffer.
* This automatically clears all existing textures added previously
* with {@link FrameBuffer#addColorTexture } and adds this texture as the
* only target.
*
* @param tex The color texture array to set.
*/
public void setColorTexture(TextureArray tex, int layer){
clearColorTargets();
addColorTexture(tex, layer);
}
/**
* Set the color texture to use for this framebuffer.
* This automatically clears all existing textures added previously
@ -369,6 +387,31 @@ public class FrameBuffer extends NativeObject {
colorBufs.add(colorBuf);
}
/**
* Add a color texture array to use for this framebuffer.
* If MRT is enabled, then each subsequently added texture can be
* rendered to through a shader that writes to the array <code>gl_FragData</code>.
* If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) }
* is rendered to by the shader.
*
* @param tex The texture array to add.
*/
public void addColorTexture(TextureArray tex, int layer) {
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
Image img = tex.getImage();
checkSetTexture(tex, false);
RenderBuffer colorBuf = new RenderBuffer();
colorBuf.slot = colorBufs.size();
colorBuf.tex = tex;
colorBuf.format = img.getFormat();
colorBuf.layer = layer;
colorBufs.add(colorBuf);
}
/**
* Add a color texture to use for this framebuffer.
* If MRT is enabled, then each subsequently added texture can be
@ -412,6 +455,19 @@ public class FrameBuffer extends NativeObject {
depthBuf.tex = tex;
depthBuf.format = img.getFormat();
}
public void setDepthTexture(TextureArray tex, int layer){
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
Image img = tex.getImage();
checkSetTexture(tex, true);
depthBuf = new RenderBuffer();
depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH;
depthBuf.tex = tex;
depthBuf.format = img.getFormat();
depthBuf.layer = layer;
}
/**
* @return The number of color buffers attached to this texture.

@ -68,7 +68,7 @@ import java.util.*;
* @version $Revision$
* @author Paul Speed
*/
public class SafeArrayList<E> implements List<E> {
public class SafeArrayList<E> implements List<E>, Cloneable {
// Implementing List directly to avoid accidentally acquiring
// incorrect or non-optimal behavior from AbstractList. For
@ -97,6 +97,24 @@ public class SafeArrayList<E> implements List<E> {
addAll(c);
}
public SafeArrayList<E> clone() {
try {
SafeArrayList<E> clone = (SafeArrayList<E>)super.clone();
// Clone whichever backing store is currently active
if( backingArray != null ) {
clone.backingArray = backingArray.clone();
}
if( buffer != null ) {
clone.buffer = (List<E>)((ArrayList<E>)buffer).clone();
}
return clone;
} catch( CloneNotSupportedException e ) {
throw new AssertionError();
}
}
protected final <T> T[] createArray(Class<T> type, int size) {
return (T[])java.lang.reflect.Array.newInstance(type, size);
}

@ -37,6 +37,8 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -97,6 +99,8 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class Cloner {
static Logger log = Logger.getLogger(Cloner.class.getName());
/**
* Keeps track of the objects that have been cloned so far.
*/
@ -190,14 +194,24 @@ public class Cloner {
* method called.
*/
public <T> T clone( T object, boolean useFunctions ) {
if( object == null ) {
return null;
}
if( log.isLoggable(Level.FINER) ) {
log.finer("cloning:" + object.getClass() + "@" + System.identityHashCode(object));
}
Class<T> type = objectClass(object);
// Check the index to see if we already have it
Object clone = index.get(object);
if( clone != null ) {
if( clone != null || index.containsKey(object) ) {
if( log.isLoggable(Level.FINER) ) {
log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object)
+ " as cached:" + (clone == null ? "null" : (clone.getClass() + "@" + System.identityHashCode(clone))));
}
return type.cast(clone);
}
@ -213,6 +227,15 @@ public class Cloner {
// Now call the function again to deep clone the fields
f.cloneFields(this, result, object);
if( log.isLoggable(Level.FINER) ) {
if( result == null ) {
log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object)
+ " as transformed:null");
} else {
log.finer("clone:" + object.getClass() + "@" + System.identityHashCode(object)
+ " as transformed:" + result.getClass() + "@" + System.identityHashCode(result));
}
}
return result;
}
@ -246,14 +269,20 @@ public class Cloner {
throw new IllegalArgumentException("Object is not cloneable, type:" + type);
}
if( log.isLoggable(Level.FINER) ) {
log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object)
+ " as " + clone.getClass() + "@" + System.identityHashCode(clone));
}
return type.cast(clone);
}
/**
* Sets a custom CloneFunction for the exact Java type. Note: no inheritence
* checks are performed so a function must be registered for each specific type
* that it handles. By default ListCloneFunction is registered for
* ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList.
* Sets a custom CloneFunction for implementations of the specified Java type. Some
* inheritance checks are made but no disambiguation is performed.
* <p>Note: in the general case, it is better to register against specific classes and
* not super-classes or super-interfaces unless you know specifically that they are cloneable.</p>
* <p>By default ListCloneFunction is registered for ArrayList, LinkedList, CopyOnWriteArrayList,
* Vector, Stack, and JME's SafeArrayList.</p>
*/
public <T> void setCloneFunction( Class<T> type, CloneFunction<T> function ) {
if( function == null ) {
@ -269,7 +298,42 @@ public class Cloner {
*/
@SuppressWarnings("unchecked")
public <T> CloneFunction<T> getCloneFunction( Class<T> type ) {
return (CloneFunction<T>)functions.get(type);
CloneFunction<T> result = (CloneFunction<T>)functions.get(type);
if( result == null ) {
// Do a more exhaustive search
for( Map.Entry<Class, CloneFunction> e : functions.entrySet() ) {
if( e.getKey().isAssignableFrom(type) ) {
result = e.getValue();
break;
}
}
if( result != null ) {
// Cache it for later
functions.put(type, result);
}
}
return result;
}
/**
* Forces an object to be added to the indexing cache such that attempts
* to clone the 'original' will always result in the 'clone' being returned.
* This can be used to stub out specific values from being cloned or to
* force global shared instances to be used even if the object is cloneable
* normally.
*/
public <T> void setClonedValue( T original, T clone ) {
index.put(original, clone);
}
/**
* Returns true if the specified object has already been cloned
* by this cloner during this session. Cloned objects are cached
* for later use and it's sometimes convenient to know if some
* objects have already been cloned.
*/
public boolean isCloned( Object o ) {
return index.containsKey(o);
}
/**
@ -290,6 +354,9 @@ public class Cloner {
* clone() and objects without necessarily knowing their real type.</p>
*/
public <T> T javaClone( T object ) throws CloneNotSupportedException {
if( object == null ) {
return null;
}
Method m = methodCache.get(object.getClass());
if( m == null ) {
try {

@ -114,10 +114,10 @@ MaterialDef Phong Lighting {
//For instancing
Boolean UseInstancing
Boolean BackfaceShadows: false
Boolean BackfaceShadows : false
}
Technique {
Technique {
LightMode SinglePass
LightSpace View
@ -150,7 +150,7 @@ MaterialDef Phong Lighting {
SEPARATE_TEXCOORD : SeparateTexCoord
DISCARD_ALPHA : AlphaDiscardThreshold
USE_REFLECTION : EnvMap
SPHERE_MAP : SphereMap
SPHERE_MAP : EnvMapAsSphereMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
@ -189,7 +189,7 @@ MaterialDef Phong Lighting {
SEPARATE_TEXCOORD : SeparateTexCoord
DISCARD_ALPHA : AlphaDiscardThreshold
USE_REFLECTION : EnvMap
SPHERE_MAP : SphereMap
SPHERE_MAP : EnvMapAsSphereMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
@ -210,7 +210,6 @@ MaterialDef Phong Lighting {
}
Defines {
COLOR_MAP : ColorMap
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
@ -236,7 +235,6 @@ MaterialDef Phong Lighting {
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
@ -270,7 +268,6 @@ MaterialDef Phong Lighting {
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
@ -344,10 +341,6 @@ MaterialDef Phong Lighting {
Defines {
VERTEX_COLOR : UseVertexColor
MATERIAL_COLORS : UseMaterialColors
V_TANGENT : VTangent
MINNAERT : Minnaert
WARDISO : WardIso
DIFFUSEMAP : DiffuseMap
NORMALMAP : NormalMap
SPECULARMAP : SpecularMap

@ -107,7 +107,7 @@ MaterialDef PBR Lighting {
}
Technique {
LightMode SinglePass
LightMode SinglePassAndImageBased
VertexShader GLSL100: Common/MatDefs/Light/PBRLighting.vert
FragmentShader GLSL100: Common/MatDefs/Light/PBRLighting.frag

@ -1,12 +0,0 @@
# THIS IS AN AUTO-GENERATED FILE..
# DO NOT MODIFY!
build.date=2016-03-25
git.revision=0
git.branch=unknown
git.hash=
git.hash.short=
git.tag=
name.full=jMonkeyEngine 3.1.0-UNKNOWN
version.full=3.1.0-UNKNOWN
version.number=3.1.0
version.tag=SNAPSHOT

@ -31,6 +31,7 @@
*/
package com.jme3.material.plugins;
import com.jme3.material.logic.*;
import com.jme3.asset.*;
import com.jme3.material.*;
import com.jme3.material.RenderState.BlendMode;
@ -40,6 +41,7 @@ import com.jme3.material.TechniqueDef.ShadowMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.VarType;
import com.jme3.texture.Texture;
@ -73,6 +75,7 @@ public class J3MLoader implements AssetLoader {
private Material material;
private TechniqueDef technique;
private RenderState renderState;
private ArrayList<String> presetDefines = new ArrayList<String>();
private EnumMap<Shader.ShaderType, String> shaderLanguage;
private EnumMap<Shader.ShaderType, String> shaderName;
@ -114,7 +117,11 @@ public class J3MLoader implements AssetLoader {
if (split.length != 2){
throw new IOException("LightMode statement syntax incorrect");
}
LightMode lm = LightMode.valueOf(split[1]);
if (lm == LightMode.FixedPipeline) {
throw new UnsupportedOperationException("OpenGL1 is not supported");
}
technique.setLightMode(lm);
}
@ -506,10 +513,22 @@ public class J3MLoader implements AssetLoader {
private void readDefine(String statement) throws IOException{
String[] split = statement.split(":");
if (split.length == 1){
// add preset define
technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true);
String defineName = split[0].trim();
presetDefines.add(defineName);
}else if (split.length == 2){
technique.addShaderParamDefine(split[1].trim(), split[0].trim());
String defineName = split[0].trim();
String paramName = split[1].trim();
MatParam param = materialDef.getMaterialParam(paramName);
if (param == null) {
logger.log(Level.WARNING, "In technique ''{0}'':\n"
+ "Define ''{1}'' mapped to non-existent"
+ " material parameter ''{2}'', ignoring.",
new Object[]{technique.getName(), defineName, paramName});
return;
}
VarType paramType = param.getVarType();
technique.addShaderParamDefine(paramName, paramType, defineName);
}else{
throw new IOException("Define syntax incorrect");
}
@ -574,14 +593,27 @@ public class J3MLoader implements AssetLoader {
material.setTransparent(parseBoolean(split[1]));
}
private static String createShaderPrologue(List<String> presetDefines) {
DefineList dl = new DefineList(presetDefines.size());
for (int i = 0; i < presetDefines.size(); i++) {
dl.set(i, 1);
}
StringBuilder sb = new StringBuilder();
dl.generateSource(sb, presetDefines, null);
return sb.toString();
}
private void readTechnique(Statement techStat) throws IOException{
isUseNodes = false;
String[] split = techStat.getLine().split(whitespacePattern);
if (split.length == 1) {
technique = new TechniqueDef(null);
String techniqueUniqueName = materialDef.getAssetName() + "@Default";
technique = new TechniqueDef(null, techniqueUniqueName.hashCode());
} else if (split.length == 2) {
String techName = split[1];
technique = new TechniqueDef(techName);
String techniqueUniqueName = materialDef.getAssetName() + "@" + techName;
technique = new TechniqueDef(techName, techniqueUniqueName.hashCode());
} else {
throw new IOException("Technique statement syntax incorrect");
}
@ -592,7 +624,12 @@ public class J3MLoader implements AssetLoader {
if(isUseNodes){
nodesLoaderDelegate.computeConditions();
//used for caching later, the shader here is not a file.
// KIRILL 9/19/2015
// Not sure if this is needed anymore, since shader caching
// is now done by TechniqueDef.
technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
}
@ -600,10 +637,30 @@ public class J3MLoader implements AssetLoader {
technique.setShaderFile(shaderName, shaderLanguage);
}
technique.setShaderPrologue(createShaderPrologue(presetDefines));
switch (technique.getLightMode()) {
case Disable:
technique.setLogic(new DefaultTechniqueDefLogic(technique));
break;
case MultiPass:
technique.setLogic(new MultiPassLightingLogic(technique));
break;
case SinglePass:
technique.setLogic(new SinglePassLightingLogic(technique));
break;
case SinglePassAndImageBased:
technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique));
break;
default:
throw new UnsupportedOperationException();
}
materialDef.addTechniqueDef(technique);
technique = null;
shaderLanguage.clear();
shaderName.clear();
presetDefines.clear();
}
private void loadFromRoot(List<Statement> roots) throws IOException{

@ -44,6 +44,7 @@ import com.jme3.shader.ShaderNodeDefinition;
import com.jme3.shader.ShaderNodeVariable;
import com.jme3.shader.ShaderUtils;
import com.jme3.shader.UniformBinding;
import com.jme3.shader.VarType;
import com.jme3.shader.VariableMapping;
import com.jme3.util.blockparser.Statement;
import java.io.IOException;
@ -583,7 +584,7 @@ public class ShaderNodeLoaderDelegate {
//multiplicity is not an int attempting to find for a material parameter.
MatParam mp = findMatParam(multiplicity);
if (mp != null) {
addDefine(multiplicity);
addDefine(multiplicity, VarType.Int);
multiplicity = multiplicity.toUpperCase();
} else {
throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement);
@ -625,9 +626,9 @@ public class ShaderNodeLoaderDelegate {
*
* @param paramName
*/
public void addDefine(String paramName) {
public void addDefine(String paramName, VarType paramType) {
if (techniqueDef.getShaderParamDefine(paramName) == null) {
techniqueDef.addShaderParamDefine(paramName, paramName.toUpperCase());
techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase());
}
}
@ -660,7 +661,7 @@ public class ShaderNodeLoaderDelegate {
for (String string : defines) {
MatParam param = findMatParam(string);
if (param != null) {
addDefine(param.getName());
addDefine(param.getName(), param.getVarType());
} else {
throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement);
}
@ -752,6 +753,7 @@ public class ShaderNodeLoaderDelegate {
}
right.setNameSpace(node.getName());
right.setType(var.getType());
right.setMultiplicity(var.getMultiplicity());
mapping.setRightVariable(right);
storeVaryings(node, mapping.getRightVariable());

@ -0,0 +1,52 @@
/*
* 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.asset;
import com.jme3.asset.plugins.ClasspathLocator;
import com.jme3.shader.plugins.GLSLLoader;
import com.jme3.system.JmeSystem;
import com.jme3.system.MockJmeSystemDelegate;
import org.junit.Test;
public class LoadShaderSourceTest {
@Test
public void testLoadShaderSource() {
JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
AssetManager assetManager = new DesktopAssetManager();
assetManager.registerLocator(null, ClasspathLocator.class);
assetManager.registerLoader(GLSLLoader.class, "frag");
String showNormals = (String) assetManager.loadAsset("Common/MatDefs/Misc/ShowNormals.frag");
System.out.println(showNormals);
}
}

@ -0,0 +1,538 @@
/*
* 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.material;
import com.jme3.asset.AssetManager;
import com.jme3.light.LightList;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.VarType;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Assert;
import org.junit.Test;
import static com.jme3.scene.MPOTestUtils.*;
import com.jme3.shader.DefineList;
import com.jme3.system.NullRenderer;
import com.jme3.system.TestUtil;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import java.util.HashMap;
import java.util.Map;
/**
* Validates how {@link MatParamOverride MPOs} work on the material level.
*
* @author Kirill Vainer
*/
public class MaterialMatParamOverrideTest {
private static final HashSet<String> IGNORED_UNIFORMS = new HashSet<String>(
Arrays.asList(new String[]{"m_ParallaxHeight", "m_Shininess", "m_BackfaceShadows"}));
@Test
public void testBoolMpoOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoBool("UseMaterialColors", true));
outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
}
@Test
public void testBoolMpOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoBool("UseMaterialColors", true));
outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
}
@Test
public void testBoolOverrideFalse() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoBool("UseMaterialColors", true));
inputMpo(mpoBool("UseMaterialColors", false));
outDefines();
outUniforms(uniform("UseMaterialColors", VarType.Boolean, false));
}
@Test
public void testBoolOverrideTrue() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoBool("UseMaterialColors", false));
inputMpo(mpoBool("UseMaterialColors", true));
outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
}
@Test
public void testFloatMpoOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoFloat("AlphaDiscardThreshold", 3.12f));
outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
}
@Test
public void testFloatMpOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
}
@Test
public void testFloatOverride() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
inputMpo(mpoFloat("AlphaDiscardThreshold", 2.79f));
outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
}
@Test
public void testMpoDisable() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
MatParamOverride override = mpoFloat("AlphaDiscardThreshold", 2.79f);
inputMpo(override);
outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
reset();
override.setEnabled(false);
outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
reset();
override.setEnabled(true);
outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
}
@Test
public void testIntMpoOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoInt("NumberOfBones", 1234));
outDefines(def("NUM_BONES", VarType.Int, 1234));
outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
}
@Test
public void testIntMpOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoInt("NumberOfBones", 1234));
outDefines(def("NUM_BONES", VarType.Int, 1234));
outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
}
@Test
public void testIntOverride() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMp(mpoInt("NumberOfBones", 1234));
inputMpo(mpoInt("NumberOfBones", 4321));
outDefines(def("NUM_BONES", VarType.Int, 4321));
outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
}
@Test
public void testMatrixArray() {
Matrix4f[] matrices = new Matrix4f[]{
new Matrix4f()
};
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoMatrix4Array("BoneMatrices", matrices));
outDefines();
outUniforms(uniform("BoneMatrices", VarType.Matrix4Array, matrices));
}
@Test
public void testNonExistentParameter() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoInt("NonExistent", 4321));
outDefines();
outUniforms();
}
@Test
public void testWrongType() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoInt("UseMaterialColors", 4321));
outDefines();
outUniforms();
}
@Test
public void testParamOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
inputMpo(mpoFloat("ShadowMapSize", 3.12f));
outDefines();
outUniforms(uniform("ShadowMapSize", VarType.Float, 3.12f));
}
@Test
public void testRemove() {
material("Common/MatDefs/Light/Lighting.j3md");
reset();
inputMp(mpoInt("NumberOfBones", 1234));
outDefines(def("NUM_BONES", VarType.Int, 1234));
outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
reset();
inputMpo(mpoInt("NumberOfBones", 4321));
outDefines(def("NUM_BONES", VarType.Int, 4321));
outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
reset();
geometry.clearMatParamOverrides();
geometry.updateGeometricState();
outDefines(def("NUM_BONES", VarType.Int, 1234));
outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
reset();
geometry.getMaterial().clearParam("NumberOfBones");
outDefines();
outUniforms();
reset();
inputMpo(mpoInt("NumberOfBones", 4321));
outDefines(def("NUM_BONES", VarType.Int, 4321));
outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
reset();
inputMp(mpoInt("NumberOfBones", 1234));
outDefines(def("NUM_BONES", VarType.Int, 4321));
outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
}
public void testRemoveOverride() {
material("Common/MatDefs/Light/Lighting.j3md");
reset();
inputMp(mpoInt("NumberOfBones", 1234));
outDefines(def("NUM_BONES", VarType.Int, 1234));
outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
reset();
inputMpo(mpoInt("NumberOfBones", 4321));
outDefines(def("NUM_BONES", VarType.Int, 4321));
outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
reset();
geometry.clearMatParamOverrides();
outDefines(def("NUM_BONES", VarType.Int, 1234));
outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
}
@Test
public void testRemoveMpoOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
reset();
inputMpo(mpoInt("NumberOfBones", 4321));
outDefines(def("NUM_BONES", VarType.Int, 4321));
outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
reset();
geometry.clearMatParamOverrides();
geometry.updateGeometricState();
outDefines();
outUniforms();
}
@Test
public void testTextureMpoOnly() {
material("Common/MatDefs/Light/Lighting.j3md");
Texture2D tex = new Texture2D(128, 128, Format.RGBA8);
inputMpo(mpoTexture2D("DiffuseMap", tex));
outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex));
outUniforms(uniform("DiffuseMap", VarType.Int, 0));
outTextures(tex);
}
@Test
public void testTextureOverride() {
material("Common/MatDefs/Light/Lighting.j3md");
Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8);
Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8);
inputMp(mpoTexture2D("DiffuseMap", tex1));
inputMpo(mpoTexture2D("DiffuseMap", tex2));
outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2));
outUniforms(uniform("DiffuseMap", VarType.Int, 0));
outTextures(tex2);
}
@Test
public void testRemoveTexture() {
material("Common/MatDefs/Light/Lighting.j3md");
Texture2D tex = new Texture2D(128, 128, Format.RGBA8);
reset();
inputMpo(mpoTexture2D("DiffuseMap", tex));
outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex));
outUniforms(uniform("DiffuseMap", VarType.Int, 0));
outTextures(tex);
reset();
geometry.clearMatParamOverrides();
geometry.updateGeometricState();
outDefines();
outUniforms();
outTextures();
}
@Test
public void testRemoveTextureOverride() {
material("Common/MatDefs/Light/Lighting.j3md");
Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8);
Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8);
reset();
inputMp(mpoTexture2D("DiffuseMap", tex1));
outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1));
outUniforms(uniform("DiffuseMap", VarType.Int, 0));
outTextures(tex1);
reset();
inputMpo(mpoTexture2D("DiffuseMap", tex2));
outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2));
outUniforms(uniform("DiffuseMap", VarType.Int, 0));
outTextures(tex2);
reset();
geometry.clearMatParamOverrides();
geometry.updateGeometricState();
outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1));
outUniforms(uniform("DiffuseMap", VarType.Int, 0));
outTextures(tex1);
}
private static final class Define {
public String name;
public VarType type;
public Object value;
@Override
public int hashCode() {
int hash = 3;
hash = 89 * hash + this.name.hashCode();
hash = 89 * hash + this.type.hashCode();
hash = 89 * hash + this.value.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
final Define other = (Define) obj;
return this.name.equals(other.name) && this.type.equals(other.type) && this.value.equals(other.value);
}
}
private final Geometry geometry = new Geometry("geometry", new Box(1, 1, 1));
private final LightList lightList = new LightList(geometry);
private final NullRenderer renderer = new NullRenderer() {
@Override
public void setShader(Shader shader) {
MaterialMatParamOverrideTest.this.usedShader = shader;
evaluated = true;
}
@Override
public void setTexture(int unit, Texture texture) {
MaterialMatParamOverrideTest.this.usedTextures[unit] = texture;
}
};
private final RenderManager renderManager = new RenderManager(renderer);
private boolean evaluated = false;
private Shader usedShader = null;
private final Texture[] usedTextures = new Texture[32];
private void inputMp(MatParam... params) {
if (evaluated) {
throw new IllegalStateException();
}
Material mat = geometry.getMaterial();
for (MatParam param : params) {
mat.setParam(param.getName(), param.getVarType(), param.getValue());
}
}
private void inputMpo(MatParamOverride... overrides) {
if (evaluated) {
throw new IllegalStateException();
}
for (MatParamOverride override : overrides) {
geometry.addMatParamOverride(override);
}
geometry.updateGeometricState();
}
private void reset() {
evaluated = false;
usedShader = null;
Arrays.fill(usedTextures, null);
}
private Define def(String name, VarType type, Object value) {
Define d = new Define();
d.name = name;
d.type = type;
d.value = value;
return d;
}
private Uniform uniform(String name, VarType type, Object value) {
Uniform u = new Uniform();
u.setName("m_" + name);
u.setValue(type, value);
return u;
}
private void material(String path) {
AssetManager assetManager = TestUtil.createAssetManager();
geometry.setMaterial(new Material(assetManager, path));
}
private void evaluateTechniqueDef() {
Assert.assertFalse(evaluated);
Material mat = geometry.getMaterial();
mat.render(geometry, lightList, renderManager);
Assert.assertTrue(evaluated);
}
private void outTextures(Texture... textures) {
for (int i = 0; i < usedTextures.length; i++) {
if (i < textures.length) {
Assert.assertSame(textures[i], usedTextures[i]);
} else {
Assert.assertNull(usedTextures[i]);
}
}
}
private void outDefines(Define... expectedDefinesArray) {
Map<String, Define> nameToDefineMap = new HashMap<String, Define>();
for (Define define : expectedDefinesArray) {
nameToDefineMap.put(define.name, define);
}
if (!evaluated) {
evaluateTechniqueDef();
}
Material mat = geometry.getMaterial();
Technique tech = mat.getActiveTechnique();
TechniqueDef def = tech.getDef();
DefineList actualDefines = tech.getDynamicDefines();
String[] defineNames = def.getDefineNames();
VarType[] defineTypes = def.getDefineTypes();
Assert.assertEquals(defineNames.length, defineTypes.length);
for (int index = 0; index < defineNames.length; index++) {
String name = defineNames[index];
VarType type = defineTypes[index];
Define expectedDefine = nameToDefineMap.remove(name);
Object expectedValue = null;
if (expectedDefine != null) {
Assert.assertEquals(expectedDefine.type, type);
expectedValue = expectedDefine.value;
}
switch (type) {
case Boolean:
if (expectedValue != null) {
Assert.assertEquals((boolean) (Boolean) expectedValue, actualDefines.getBoolean(index));
} else {
Assert.assertEquals(false, actualDefines.getBoolean(index));
}
break;
case Int:
if (expectedValue != null) {
Assert.assertEquals((int) (Integer) expectedValue, actualDefines.getInt(index));
} else {
Assert.assertEquals(0, actualDefines.getInt(index));
}
break;
case Float:
if (expectedValue != null) {
Assert.assertEquals((float) (Float) expectedValue, actualDefines.getFloat(index), 0f);
} else {
Assert.assertEquals(0f, actualDefines.getFloat(index), 0f);
}
break;
default:
if (expectedValue != null) {
Assert.assertEquals(1, actualDefines.getInt(index));
} else {
Assert.assertEquals(0, actualDefines.getInt(index));
}
break;
}
}
Assert.assertTrue(nameToDefineMap.isEmpty());
}
private void outUniforms(Uniform... uniforms) {
HashSet<Uniform> actualUniforms = new HashSet<>();
for (Uniform uniform : usedShader.getUniformMap().values()) {
if (uniform.getName().startsWith("m_")
&& !IGNORED_UNIFORMS.contains(uniform.getName())) {
actualUniforms.add(uniform);
}
}
HashSet<Uniform> expectedUniforms = new HashSet<>(Arrays.asList(uniforms));
if (!expectedUniforms.equals(actualUniforms)) {
Assert.fail("Uniform lists must match: " + expectedUniforms + " != " + actualUniforms);
}
}
}

@ -33,6 +33,9 @@ package com.jme3.math;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import org.junit.Ignore;
/**
* Verifies that algorithms in {@link FastMath} are working correctly.
*
@ -56,4 +59,39 @@ public class FastMathTest {
assert nextPowerOf2 == nearestPowerOfTwoSlow(i);
}
}
private static int fastCounterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) {
float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
return (int) Math.signum(result);
}
private static Vector2f randomVector() {
return new Vector2f(FastMath.nextRandomFloat(),
FastMath.nextRandomFloat());
}
@Ignore
@Test
public void testCounterClockwise() {
for (int i = 0; i < 100; i++) {
Vector2f p0 = randomVector();
Vector2f p1 = randomVector();
Vector2f p2 = randomVector();
int fastResult = fastCounterClockwise(p0, p1, p2);
int slowResult = FastMath.counterClockwise(p0, p1, p2);
assert fastResult == slowResult;
}
// duplicate test
Vector2f p0 = new Vector2f(0,0);
Vector2f p1 = new Vector2f(0,0);
Vector2f p2 = new Vector2f(0,1);
int fastResult = fastCounterClockwise(p0, p1, p2);
int slowResult = FastMath.counterClockwise(p0, p1, p2);
assertEquals(slowResult, fastResult);
}
}

@ -0,0 +1,342 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.renderer;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Box;
import com.jme3.system.TestUtil;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class OpaqueComparatorTest {
private final Mesh mesh = new Box(1,1,1);
private Camera cam = new Camera(1, 1);
private RenderManager renderManager;
private AssetManager assetManager;
private OpaqueComparator comparator = new OpaqueComparator();
@Before
public void setUp() {
assetManager = TestUtil.createAssetManager();
renderManager = TestUtil.createRenderManager();
comparator.setCamera(cam);
}
/**
* Given a correctly sorted list of materials, check if the
* opaque comparator can sort a reversed list of them.
*
* Each material will be cloned so that none of them will be equal to each other
* in reference, forcing the comparator to compare the material sort ID.
*
* E.g. for a list of materials A, B, C, the following list will be generated:
* <pre>C, B, A, C, B, A, C, B, A</pre>, it should result in
* <pre>A, A, A, B, B, B, C, C, C</pre>.
*
* @param materials The pre-sorted list of materials to check sorting for.
*/
private void testSort(Material ... materials) {
GeometryList gl = new GeometryList(comparator);
for (int g = 0; g < 5; g++) {
for (int i = materials.length - 1; i >= 0; i--) {
Geometry geom = new Geometry("geom", mesh);
Material clonedMaterial = materials[i].clone();
if (materials[i].getActiveTechnique() != null) {
String techniqueName = materials[i].getActiveTechnique().getDef().getName();
clonedMaterial.selectTechnique(techniqueName, renderManager);
} else {
clonedMaterial.selectTechnique("Default", renderManager);
}
geom.setMaterial(clonedMaterial);
gl.add(geom);
}
}
gl.sort();
for (int i = 0; i < gl.size(); i++) {
Material mat = gl.get(i).getMaterial();
String sortId = Integer.toHexString(mat.getSortId()).toUpperCase();
System.out.print(sortId + "\t");
System.out.println(mat);
}
Set<String> alreadySeen = new HashSet<String>();
Material current = null;
for (int i = 0; i < gl.size(); i++) {
Material mat = gl.get(i).getMaterial();
if (current == null) {
current = mat;
} else if (!current.getName().equals(mat.getName())) {
assert !alreadySeen.contains(mat.getName());
alreadySeen.add(current.getName());
current = mat;
}
}
for (int i = 0; i < materials.length; i++) {
for (int g = 0; g < 5; g++) {
int index = i * 5 + g;
Material mat1 = gl.get(index).getMaterial();
Material mat2 = materials[i];
assert mat1.getName().equals(mat2.getName()) :
mat1.getName() + " != " + mat2.getName() + " for index " + index;
}
}
}
@Test
public void testSortByMaterialDef() {
Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material particleMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
Material unshadedMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
lightingMat.setName("MatLight");
particleMat.setName("MatParticle");
unshadedMat.setName("MatUnshaded");
skyMat.setName("MatSky");
testSort(skyMat, lightingMat, particleMat, unshadedMat);
}
@Test
public void testSortByTechnique() {
Material lightingMatDefault = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingPreShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingPostShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatPreNormalPass = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatGBuf = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatGlow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
lightingMatDefault.setName("TechDefault");
lightingMatDefault.selectTechnique("Default", renderManager);
lightingPostShadow.setName("TechPostShad");
lightingPostShadow.selectTechnique("PostShadow", renderManager);
lightingPreShadow.setName("TechPreShad");
lightingPreShadow.selectTechnique("PreShadow", renderManager);
lightingMatPreNormalPass.setName("TechNorm");
lightingMatPreNormalPass.selectTechnique("PreNormalPass", renderManager);
lightingMatGBuf.setName("TechGBuf");
lightingMatGBuf.selectTechnique("GBuf", renderManager);
lightingMatGlow.setName("TechGlow");
lightingMatGlow.selectTechnique("Glow", renderManager);
testSort(lightingMatGlow, lightingPreShadow, lightingMatPreNormalPass,
lightingMatDefault, lightingPostShadow, lightingMatGBuf);
}
@Test(expected = AssertionError.class)
public void testNoSortByParam() {
Material sameMat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Material sameMat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
sameMat1.setName("MatRed");
sameMat1.setColor("Color", ColorRGBA.Red);
sameMat2.setName("MatBlue");
sameMat2.setColor("Color", ColorRGBA.Blue);
testSort(sameMat1, sameMat2);
}
private Texture createTexture(String name) {
ByteBuffer bb = BufferUtils.createByteBuffer(3);
Image image = new Image(Format.RGB8, 1, 1, bb, ColorSpace.sRGB);
Texture2D texture = new Texture2D(image);
texture.setName(name);
return texture;
}
@Test
public void testSortByTexture() {
Material texture1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Material texture2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Material texture3Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Texture tex1 = createTexture("A");
tex1.getImage().setId(1);
Texture tex2 = createTexture("B");
tex2.getImage().setId(2);
Texture tex3 = createTexture("C");
tex3.getImage().setId(3);
texture1Mat.setName("TexA");
texture1Mat.setTexture("ColorMap", tex1);
texture2Mat.setName("TexB");
texture2Mat.setTexture("ColorMap", tex2);
texture3Mat.setName("TexC");
texture3Mat.setTexture("ColorMap", tex3);
testSort(texture1Mat, texture2Mat, texture3Mat);
}
@Test
public void testSortByShaderDefines() {
Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatVColor = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatVLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatTC = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material lightingMatTCVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
lightingMat.setName("DefNone");
lightingMatVColor.setName("DefVC");
lightingMatVColor.setBoolean("UseVertexColor", true);
lightingMatVLight.setName("DefVL");
lightingMatVLight.setBoolean("VertexLighting", true);
lightingMatTC.setName("DefTC");
lightingMatTC.setBoolean("SeparateTexCoord", true);
lightingMatVColorLight.setName("DefVCVL");
lightingMatVColorLight.setBoolean("UseVertexColor", true);
lightingMatVColorLight.setBoolean("VertexLighting", true);
lightingMatTCVColorLight.setName("DefVCVLTC");
lightingMatTCVColorLight.setBoolean("UseVertexColor", true);
lightingMatTCVColorLight.setBoolean("VertexLighting", true);
lightingMatTCVColorLight.setBoolean("SeparateTexCoord", true);
testSort(lightingMat, lightingMatVColor, lightingMatVLight,
lightingMatVColorLight, lightingMatTC, lightingMatTCVColorLight);
}
@Test
public void testSortByAll() {
Material matBase1 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material matBase2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Texture texBase = createTexture("BASE");
texBase.getImage().setId(1);
Texture tex1 = createTexture("1");
tex1.getImage().setId(2);
Texture tex2 = createTexture("2");
tex2.getImage().setId(3);
matBase1.setName("BASE");
matBase1.selectTechnique("Default", renderManager);
matBase1.setBoolean("UseVertexColor", true);
matBase1.setTexture("DiffuseMap", texBase);
Material mat1100 = matBase1.clone();
mat1100.setName("1100");
mat1100.selectTechnique("PreShadow", renderManager);
Material mat1101 = matBase1.clone();
mat1101.setName("1101");
mat1101.selectTechnique("PreShadow", renderManager);
mat1101.setTexture("DiffuseMap", tex1);
Material mat1102 = matBase1.clone();
mat1102.setName("1102");
mat1102.selectTechnique("PreShadow", renderManager);
mat1102.setTexture("DiffuseMap", tex2);
Material mat1110 = matBase1.clone();
mat1110.setName("1110");
mat1110.selectTechnique("PreShadow", renderManager);
mat1110.setFloat("AlphaDiscardThreshold", 2f);
Material mat1120 = matBase1.clone();
mat1120.setName("1120");
mat1120.selectTechnique("PreShadow", renderManager);
mat1120.setBoolean("UseInstancing", true);
Material mat1121 = matBase1.clone();
mat1121.setName("1121");
mat1121.selectTechnique("PreShadow", renderManager);
mat1121.setBoolean("UseInstancing", true);
mat1121.setTexture("DiffuseMap", tex1);
Material mat1122 = matBase1.clone();
mat1122.setName("1122");
mat1122.selectTechnique("PreShadow", renderManager);
mat1122.setBoolean("UseInstancing", true);
mat1122.setTexture("DiffuseMap", tex2);
Material mat1140 = matBase1.clone();
mat1140.setName("1140");
mat1140.selectTechnique("PreShadow", renderManager);
mat1140.setFloat("AlphaDiscardThreshold", 2f);
mat1140.setBoolean("UseInstancing", true);
Material mat1200 = matBase1.clone();
mat1200.setName("1200");
mat1200.selectTechnique("PostShadow", renderManager);
Material mat1210 = matBase1.clone();
mat1210.setName("1210");
mat1210.selectTechnique("PostShadow", renderManager);
mat1210.setFloat("AlphaDiscardThreshold", 2f);
Material mat1220 = matBase1.clone();
mat1220.setName("1220");
mat1220.selectTechnique("PostShadow", renderManager);
mat1220.setBoolean("UseInstancing", true);
Material mat2000 = matBase2.clone();
mat2000.setName("2000");
testSort(mat1100, mat1101, mat1102, mat1110,
mat1120, mat1121, mat1122, mat1140,
mat1200, mat1210, mat1220, mat2000);
}
}

@ -0,0 +1,173 @@
/*
* 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.scene;
import com.jme3.material.MatParamOverride;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.Camera;
import com.jme3.shader.VarType;
import com.jme3.texture.Texture2D;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
public class MPOTestUtils {
private static final Camera DUMMY_CAM = new Camera(640, 480);
private static final SceneGraphVisitor VISITOR = new SceneGraphVisitor() {
@Override
public void visit(Spatial spatial) {
validateSubScene(spatial);
}
};
private static void validateSubScene(Spatial scene) {
scene.checkCulling(DUMMY_CAM);
Set<MatParamOverride> actualOverrides = new HashSet<MatParamOverride>();
for (MatParamOverride override : scene.getWorldMatParamOverrides()) {
actualOverrides.add(override);
}
Set<MatParamOverride> expectedOverrides = new HashSet<MatParamOverride>();
Spatial current = scene;
while (current != null) {
for (MatParamOverride override : current.getLocalMatParamOverrides()) {
expectedOverrides.add(override);
}
current = current.getParent();
}
assertEquals("For " + scene, expectedOverrides, actualOverrides);
}
public static void validateScene(Spatial scene) {
scene.updateGeometricState();
scene.depthFirstTraversal(VISITOR);
}
public static MatParamOverride mpoInt(String name, int value) {
return new MatParamOverride(VarType.Int, name, value);
}
public static MatParamOverride mpoBool(String name, boolean value) {
return new MatParamOverride(VarType.Boolean, name, value);
}
public static MatParamOverride mpoFloat(String name, float value) {
return new MatParamOverride(VarType.Float, name, value);
}
public static MatParamOverride mpoMatrix4Array(String name, Matrix4f[] value) {
return new MatParamOverride(VarType.Matrix4Array, name, value);
}
public static MatParamOverride mpoTexture2D(String name, Texture2D texture) {
return new MatParamOverride(VarType.Texture2D, name, texture);
}
private static int getRefreshFlags(Spatial scene) {
try {
Field refreshFlagsField = Spatial.class.getDeclaredField("refreshFlags");
refreshFlagsField.setAccessible(true);
return (Integer) refreshFlagsField.get(scene);
} catch (NoSuchFieldException ex) {
throw new AssertionError(ex);
} catch (SecurityException ex) {
throw new AssertionError(ex);
} catch (IllegalArgumentException ex) {
throw new AssertionError(ex);
} catch (IllegalAccessException ex) {
throw new AssertionError(ex);
}
}
private static void dumpSceneRF(Spatial scene, String indent, boolean last, int refreshFlagsMask) {
StringBuilder sb = new StringBuilder();
sb.append(indent);
if (last) {
if (!indent.isEmpty()) {
sb.append("└─");
} else {
sb.append(" ");
}
indent += " ";
} else {
sb.append("├─");
indent += "│ ";
}
sb.append(scene.getName());
int rf = getRefreshFlags(scene) & refreshFlagsMask;
if (rf != 0) {
sb.append("(");
if ((rf & 0x1) != 0) {
sb.append("T");
}
if ((rf & 0x2) != 0) {
sb.append("B");
}
if ((rf & 0x4) != 0) {
sb.append("L");
}
if ((rf & 0x8) != 0) {
sb.append("l");
}
if ((rf & 0x10) != 0) {
sb.append("O");
}
sb.append(")");
}
if (!scene.getLocalMatParamOverrides().isEmpty()) {
sb.append(" [MPO]");
}
System.out.println(sb);
if (scene instanceof Node) {
Node node = (Node) scene;
int childIndex = 0;
for (Spatial child : node.getChildren()) {
boolean childLast = childIndex == node.getQuantity() - 1;
dumpSceneRF(child, indent, childLast, refreshFlagsMask);
childIndex++;
}
}
}
public static void dumpSceneRF(Spatial scene, int refreshFlagsMask) {
dumpSceneRF(scene, "", true, refreshFlagsMask);
}
}

@ -0,0 +1,278 @@
/*
* 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.scene;
import com.jme3.asset.AssetManager;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.material.MatParamOverride;
import org.junit.Test;
import static com.jme3.scene.MPOTestUtils.*;
import static org.junit.Assert.*;
import com.jme3.system.TestUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Validates how {@link MatParamOverride MPOs} work on the scene level.
*
* @author Kirill Vainer
*/
public class SceneMatParamOverrideTest {
private static Node createDummyScene() {
Node scene = new Node("Scene Node");
Node a = new Node("A");
Node b = new Node("B");
Node c = new Node("C");
Node d = new Node("D");
Node e = new Node("E");
Node f = new Node("F");
Node g = new Node("G");
Node h = new Node("H");
Node j = new Node("J");
scene.attachChild(a);
scene.attachChild(b);
a.attachChild(c);
a.attachChild(d);
b.attachChild(e);
b.attachChild(f);
c.attachChild(g);
c.attachChild(h);
c.attachChild(j);
return scene;
}
@Test
public void testOverrides_Empty() {
Node n = new Node("Node");
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
n.updateGeometricState();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
}
@Test
public void testOverrides_AddRemove() {
MatParamOverride override = mpoBool("Test", true);
Node n = new Node("Node");
n.removeMatParamOverride(override);
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
n.addMatParamOverride(override);
assertSame(n.getLocalMatParamOverrides().get(0), override);
assertTrue(n.getWorldMatParamOverrides().isEmpty());
n.updateGeometricState();
assertSame(n.getLocalMatParamOverrides().get(0), override);
assertSame(n.getWorldMatParamOverrides().get(0), override);
n.removeMatParamOverride(override);
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertSame(n.getWorldMatParamOverrides().get(0), override);
n.updateGeometricState();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
}
@Test
public void testOverrides_Clear() {
MatParamOverride override = mpoBool("Test", true);
Node n = new Node("Node");
n.clearMatParamOverrides();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
n.addMatParamOverride(override);
n.clearMatParamOverrides();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
n.addMatParamOverride(override);
n.updateGeometricState();
n.clearMatParamOverrides();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertSame(n.getWorldMatParamOverrides().get(0), override);
n.updateGeometricState();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
n.addMatParamOverride(override);
n.clearMatParamOverrides();
n.updateGeometricState();
assertTrue(n.getLocalMatParamOverrides().isEmpty());
assertTrue(n.getWorldMatParamOverrides().isEmpty());
}
@Test
public void testOverrides_AddAfterAttach() {
Node scene = createDummyScene();
scene.updateGeometricState();
Node root = new Node("Root Node");
root.updateGeometricState();
root.attachChild(scene);
scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
validateScene(root);
}
@Test
public void testOverrides_AddBeforeAttach() {
Node scene = createDummyScene();
scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
scene.updateGeometricState();
Node root = new Node("Root Node");
root.updateGeometricState();
root.attachChild(scene);
validateScene(root);
}
@Test
public void testOverrides_RemoveBeforeAttach() {
Node scene = createDummyScene();
scene.updateGeometricState();
Node root = new Node("Root Node");
root.updateGeometricState();
scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
validateScene(scene);
scene.getChild("A").clearMatParamOverrides();
validateScene(scene);
root.attachChild(scene);
validateScene(root);
}
@Test
public void testOverrides_RemoveAfterAttach() {
Node scene = createDummyScene();
scene.updateGeometricState();
Node root = new Node("Root Node");
root.updateGeometricState();
scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
root.attachChild(scene);
validateScene(root);
scene.getChild("A").clearMatParamOverrides();
validateScene(root);
}
@Test
public void testOverrides_IdenticalNames() {
Node scene = createDummyScene();
scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
scene.getChild("C").addMatParamOverride(mpoInt("val", 7));
validateScene(scene);
}
@Test
public void testOverrides_CloningScene_DoesntCloneMPO() {
Node originalScene = createDummyScene();
originalScene.getChild("A").addMatParamOverride(mpoInt("int", 5));
originalScene.getChild("A").addMatParamOverride(mpoBool("bool", true));
originalScene.getChild("A").addMatParamOverride(mpoFloat("float", 3.12f));
Node clonedScene = originalScene.clone(false);
validateScene(clonedScene);
validateScene(originalScene);
List<MatParamOverride> clonedOverrides = clonedScene.getChild("A").getLocalMatParamOverrides();
List<MatParamOverride> originalOverrides = originalScene.getChild("A").getLocalMatParamOverrides();
assertNotSame(clonedOverrides, originalOverrides);
assertEquals(clonedOverrides, originalOverrides);
for (int i = 0; i < clonedOverrides.size(); i++) {
assertNotSame(clonedOverrides.get(i), originalOverrides.get(i));
assertEquals(clonedOverrides.get(i), originalOverrides.get(i));
}
}
@Test
public void testOverrides_SaveAndLoad_KeepsMPOs() {
MatParamOverride override = mpoInt("val", 5);
Node scene = createDummyScene();
scene.getChild("A").addMatParamOverride(override);
AssetManager assetManager = TestUtil.createAssetManager();
Node loadedScene = BinaryExporter.saveAndLoad(assetManager, scene);
Node root = new Node("Root Node");
root.attachChild(loadedScene);
validateScene(root);
validateScene(scene);
assertNotSame(override, loadedScene.getChild("A").getLocalMatParamOverrides().get(0));
assertEquals(override, loadedScene.getChild("A").getLocalMatParamOverrides().get(0));
}
@Test
public void testEquals() {
assertEquals(mpoInt("val", 5), mpoInt("val", 5));
assertEquals(mpoBool("val", true), mpoBool("val", true));
assertNotEquals(mpoInt("val", 5), mpoInt("val", 6));
assertNotEquals(mpoInt("val1", 5), mpoInt("val2", 5));
assertNotEquals(mpoBool("val", true), mpoInt("val", 1));
}
}

@ -0,0 +1,300 @@
/*
* 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.shader;
import com.jme3.math.FastMath;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.*;
public class DefineListTest {
private static final List<String> DEFINE_NAMES = Arrays.asList("BOOL_VAR", "INT_VAR", "FLOAT_VAR");
private static final List<VarType> DEFINE_TYPES = Arrays.asList(VarType.Boolean, VarType.Int, VarType.Float);
private static final int NUM_DEFINES = DEFINE_NAMES.size();
private static final int BOOL_VAR = 0;
private static final int INT_VAR = 1;
private static final int FLOAT_VAR = 2;
private static final DefineList EMPTY = new DefineList(NUM_DEFINES);
@Test
public void testHashCollision() {
DefineList dl1 = new DefineList(64);
DefineList dl2 = new DefineList(64);
// Try to cause a hash collision
// (since bit #32 is aliased to bit #1 in 32-bit ints)
dl1.set(0, 123);
dl1.set(32, 0);
dl2.set(32, 0);
dl2.set(0, 123);
assert dl1.hashCode() == dl2.hashCode();
assert dl1.equals(dl2);
}
@Test
public void testGetSet() {
DefineList dl = new DefineList(NUM_DEFINES);
assertFalse(dl.getBoolean(BOOL_VAR));
assertEquals(dl.getInt(INT_VAR), 0);
assertEquals(dl.getFloat(FLOAT_VAR), 0f, 0f);
dl.set(BOOL_VAR, true);
dl.set(INT_VAR, -1);
dl.set(FLOAT_VAR, Float.NaN);
assertTrue(dl.getBoolean(BOOL_VAR));
assertEquals(dl.getInt(INT_VAR), -1);
assertTrue(Float.isNaN(dl.getFloat(FLOAT_VAR)));
}
private String generateSource(DefineList dl) {
StringBuilder sb = new StringBuilder();
dl.generateSource(sb, DEFINE_NAMES, DEFINE_TYPES);
return sb.toString();
}
@Test
public void testSourceInitial() {
DefineList dl = new DefineList(NUM_DEFINES);
assert dl.hashCode() == 0;
assert generateSource(dl).equals("");
}
@Test
public void testSourceBooleanDefine() {
DefineList dl = new DefineList(NUM_DEFINES);
dl.set(BOOL_VAR, true);
assert dl.hashCode() == 1;
assert generateSource(dl).equals("#define BOOL_VAR 1\n");
dl.set(BOOL_VAR, false);
assert dl.hashCode() == 0;
assert generateSource(dl).equals("");
}
@Test
public void testSourceIntDefine() {
DefineList dl = new DefineList(NUM_DEFINES);
int hashCodeWithInt = 1 << INT_VAR;
dl.set(INT_VAR, 123);
assert dl.hashCode() == hashCodeWithInt;
assert generateSource(dl).equals("#define INT_VAR 123\n");
dl.set(INT_VAR, 0);
assert dl.hashCode() == 0;
assert generateSource(dl).equals("");
dl.set(INT_VAR, -99);
assert dl.hashCode() == hashCodeWithInt;
assert generateSource(dl).equals("#define INT_VAR -99\n");
dl.set(INT_VAR, Integer.MAX_VALUE);
assert dl.hashCode() == hashCodeWithInt;
assert generateSource(dl).equals("#define INT_VAR 2147483647\n");
}
@Test
public void testSourceFloatDefine() {
DefineList dl = new DefineList(NUM_DEFINES);
dl.set(FLOAT_VAR, 1f);
assert dl.hashCode() == (1 << FLOAT_VAR);
assert generateSource(dl).equals("#define FLOAT_VAR 1.0\n");
dl.set(FLOAT_VAR, 0f);
assert dl.hashCode() == 0;
assert generateSource(dl).equals("");
dl.set(FLOAT_VAR, -1f);
assert generateSource(dl).equals("#define FLOAT_VAR -1.0\n");
dl.set(FLOAT_VAR, FastMath.FLT_EPSILON);
assert generateSource(dl).equals("#define FLOAT_VAR 1.1920929E-7\n");
dl.set(FLOAT_VAR, FastMath.PI);
assert generateSource(dl).equals("#define FLOAT_VAR 3.1415927\n");
try {
dl.set(FLOAT_VAR, Float.NaN);
generateSource(dl);
assert false;
} catch (IllegalArgumentException ex) { }
try {
dl.set(FLOAT_VAR, Float.POSITIVE_INFINITY);
generateSource(dl);
assert false;
} catch (IllegalArgumentException ex) { }
try {
dl.set(FLOAT_VAR, Float.NEGATIVE_INFINITY);
generateSource(dl);
assert false;
} catch (IllegalArgumentException ex) { }
}
@Test
public void testEqualsAndHashCode() {
DefineList dl1 = new DefineList(NUM_DEFINES);
DefineList dl2 = new DefineList(NUM_DEFINES);
assertTrue(dl1.hashCode() == 0);
assertEquals(dl1, dl2);
dl1.set(BOOL_VAR, true);
assertTrue(dl1.hashCode() == 1);
assertNotSame(dl1, dl2);
dl2.set(BOOL_VAR, true);
assertEquals(dl1, dl2);
dl1.set(INT_VAR, 2);
assertTrue(dl1.hashCode() == (1|2));
assertNotSame(dl1, dl2);
dl2.set(INT_VAR, 2);
assertEquals(dl1, dl2);
dl1.set(BOOL_VAR, false);
assertTrue(dl1.hashCode() == 2);
assertNotSame(dl1, dl2);
}
@Test
public void testDeepClone() {
DefineList dl1 = new DefineList(NUM_DEFINES);
DefineList dl2 = dl1.deepClone();
assertFalse(dl1 == dl2);
assertTrue(dl1.equals(dl2));
assertTrue(dl1.hashCode() == dl2.hashCode());
dl1.set(BOOL_VAR, true);
dl2 = dl1.deepClone();
assertTrue(dl1.equals(dl2));
assertTrue(dl1.hashCode() == dl2.hashCode());
dl1.set(INT_VAR, 123);
assertFalse(dl1.equals(dl2));
assertFalse(dl1.hashCode() == dl2.hashCode());
dl2 = dl1.deepClone();
assertTrue(dl1.equals(dl2));
assertTrue(dl1.hashCode() == dl2.hashCode());
}
@Test
public void testGenerateSource() {
DefineList dl = new DefineList(NUM_DEFINES);
assertEquals("", generateSource(dl));
dl.set(BOOL_VAR, true);
assertEquals("#define BOOL_VAR 1\n", generateSource(dl));
dl.set(INT_VAR, 123);
assertEquals("#define BOOL_VAR 1\n" +
"#define INT_VAR 123\n", generateSource(dl));
dl.set(BOOL_VAR, false);
assertEquals("#define INT_VAR 123\n", generateSource(dl));
dl.set(BOOL_VAR, true);
// should have predictable ordering based on defineId
assertEquals("#define BOOL_VAR 1\n" +
"#define INT_VAR 123\n", generateSource(dl));
}
private static String doLookup(HashMap<DefineList, String> map, boolean boolVal, int intVal, float floatVal) {
DefineList dl = new DefineList(NUM_DEFINES);
dl.set(BOOL_VAR, boolVal);
dl.set(INT_VAR, intVal);
dl.set(FLOAT_VAR, floatVal);
return map.get(dl);
}
@Test
public void testHashLookup() {
String STR_EMPTY = "This is an empty define list";
String STR_INT = "This define list has an int value";
String STR_BOOL = "This define list just has boolean value set";
String STR_BOOL_INT = "This define list has both a boolean and int value";
String STR_BOOL_INT_FLOAT = "This define list has a boolean, int, and float value";
HashMap<DefineList, String> map = new HashMap<DefineList, String>();
DefineList lookup = new DefineList(NUM_DEFINES);
map.put(lookup.deepClone(), STR_EMPTY);
lookup.set(BOOL_VAR, true);
map.put(lookup.deepClone(), STR_BOOL);
lookup.set(BOOL_VAR, false);
lookup.set(INT_VAR, 123);
map.put(lookup.deepClone(), STR_INT);
lookup.set(BOOL_VAR, true);
map.put(lookup.deepClone(), STR_BOOL_INT);
lookup.set(FLOAT_VAR, FastMath.PI);
map.put(lookup.deepClone(), STR_BOOL_INT_FLOAT);
assertEquals(doLookup(map, false, 0, 0f), STR_EMPTY);
assertEquals(doLookup(map, false, 123, 0f), STR_INT);
assertEquals(doLookup(map, true, 0, 0f), STR_BOOL);
assertEquals(doLookup(map, true, 123, 0f), STR_BOOL_INT);
assertEquals(doLookup(map, true, 123, FastMath.PI), STR_BOOL_INT_FLOAT);
}
}

@ -0,0 +1,78 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.system;
import com.jme3.audio.AudioRenderer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.ByteBuffer;
public class MockJmeSystemDelegate extends JmeSystemDelegate {
@Override
public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException {
}
@Override
public void showErrorDialog(String message) {
}
@Override
public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
return false;
}
@Override
public URL getPlatformAssetConfigURL() {
return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/General.cfg");
}
@Override
public JmeContext newContext(AppSettings settings, JmeContext.Type contextType) {
return null;
}
@Override
public AudioRenderer newAudioRenderer(AppSettings settings) {
return null;
}
@Override
public void initialize(AppSettings settings) {
}
@Override
public void showSoftKeyboard(boolean show) {
}
}

@ -0,0 +1,55 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.system;
import com.jme3.asset.AssetConfig;
import com.jme3.asset.AssetManager;
import com.jme3.asset.DesktopAssetManager;
import com.jme3.renderer.RenderManager;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TestUtil {
static {
JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
}
public static AssetManager createAssetManager() {
Logger.getLogger(AssetConfig.class.getName()).setLevel(Level.OFF);
return new DesktopAssetManager(true);
}
public static RenderManager createRenderManager() {
return new RenderManager(new NullRenderer());
}
}

@ -6,11 +6,12 @@ import com.jme3.asset.plugins.FileLocator;
import com.jme3.material.MaterialDef;
import com.jme3.material.TechniqueDef;
import com.jme3.material.plugins.J3MLoader;
import com.jme3.renderer.Caps;
import com.jme3.shader.DefineList;
import com.jme3.shader.Shader;
import com.jme3.shader.ShaderKey;
import com.jme3.shader.plugins.GLSLLoader;
import com.jme3.system.JmeSystem;
import java.util.EnumSet;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -33,23 +34,22 @@ public class ShaderCheck {
assetManager.registerLoader(GLSLLoader.class, "vert", "frag","geom","tsctrl","tseval","glsllib");
}
private static void checkMatDef(String matdefName){
private static void checkMatDef(String matdefName) {
MaterialDef def = (MaterialDef) assetManager.loadAsset(matdefName);
for (TechniqueDef techDef : def.getDefaultTechniques()){
DefineList dl = new DefineList();
dl.addFrom(techDef.getShaderPresetDefines());
ShaderKey shaderKey = new ShaderKey(dl,techDef.getShaderProgramLanguages(),techDef.getShaderProgramNames());
Shader shader = assetManager.loadShader(shaderKey);
for (Validator validator : validators){
EnumSet<Caps> rendererCaps = EnumSet.noneOf(Caps.class);
rendererCaps.add(Caps.GLSL100);
for (TechniqueDef techDef : def.getDefaultTechniques()) {
DefineList defines = techDef.createDefineList();
Shader shader = techDef.getShader(assetManager, rendererCaps, defines);
for (Validator validator : validators) {
StringBuilder sb = new StringBuilder();
validator.validate(shader, sb);
System.out.println("==== Validator: " + validator.getName() + " " +
validator.getInstalledVersion() + " ====");
System.out.println("==== Validator: " + validator.getName() + " "
+ validator.getInstalledVersion() + " ====");
System.out.println(sb.toString());
}
}
throw new UnsupportedOperationException();
}
public static void main(String[] args){

@ -50,12 +50,12 @@ import javax.swing.SwingUtilities;
*/
public class AppletHarness extends Applet {
public static final HashMap<Application, Applet> appToApplet
= new HashMap<Application, Applet>();
public static final HashMap<LegacyApplication, Applet> appToApplet
= new HashMap<LegacyApplication, Applet>();
protected JmeCanvasContext context;
protected Canvas canvas;
protected Application app;
protected LegacyApplication app;
protected String appClass;
protected URL appCfg = null;
@ -103,7 +103,7 @@ public class AppletHarness extends Applet {
JmeSystem.setLowPermissions(true);
try{
Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
app = clazz.newInstance();
}catch (ClassNotFoundException ex){
ex.printStackTrace();

@ -43,6 +43,7 @@ import com.jme3.system.JmeContext.Type;
import com.jme3.util.Screenshots;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
@ -116,12 +117,16 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
@Override
public void showErrorDialog(String message) {
final String msg = message;
EventQueue.invokeLater(new Runnable() {
public void run() {
ErrorDialog.showDialog(msg);
}
});
if (!GraphicsEnvironment.isHeadless()) {
final String msg = message;
EventQueue.invokeLater(new Runnable() {
public void run() {
ErrorDialog.showDialog(msg);
}
});
} else {
System.err.println("[JME ERROR] " + message);
}
}
@Override
@ -129,6 +134,9 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
if (SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Cannot run from EDT");
}
if (GraphicsEnvironment.isHeadless()) {
throw new IllegalStateException("Cannot show dialog in headless environment");
}
final AppSettings settings = new AppSettings(false);
settings.copyFrom(sourceSettings);
@ -333,27 +341,13 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
if (initialized) {
return;
}
initialized = true;
try {
if (!lowPermissions) {
// can only modify logging settings
// if permissions are available
// JmeFormatter formatter = new JmeFormatter();
// Handler fileHandler = new FileHandler("jme.log");
// fileHandler.setFormatter(formatter);
// Logger.getLogger("").addHandler(fileHandler);
// Handler consoleHandler = new ConsoleHandler();
// consoleHandler.setFormatter(formatter);
// Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
// Logger.getLogger("").addHandler(consoleHandler);
logger.log(Level.INFO, getBuildInfo());
if (!lowPermissions) {
if (NativeLibraryLoader.isUsingNativeBullet()) {
NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
}
// } catch (IOException ex){
// logger.log(Level.SEVERE, "I/O Error while creating log file", ex);
} catch (SecurityException ex) {
logger.log(Level.SEVERE, "Security error in creating log file", ex);
}
logger.log(Level.INFO, getBuildInfo());
}
@Override

@ -11,6 +11,11 @@ task run(dependsOn: 'build', type:JavaExec) {
jvmArgs "-XstartOnFirstThread"
jvmArgs "-Djava.awt.headless=true"
}
if (System.properties['java.util.logging.config.file'] != null) {
systemProperty "java.util.logging.config.file", System.properties['java.util.logging.config.file']
}
if( assertions == "true" ){
enableAssertions = true;
}

@ -32,19 +32,19 @@
package jme3test.app;
import com.jme3.app.Application;
import com.jme3.app.LegacyApplication;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext.Type;
/**
* Test Application functionality, such as create, restart, destroy, etc.
* Test LegacyApplication functionality, such as create, restart, destroy, etc.
* @author Kirill
*/
public class TestApplication {
public static void main(String[] args) throws InterruptedException{
System.out.println("Creating application..");
Application app = new Application();
LegacyApplication app = new LegacyApplication();
System.out.println("Starting application in LWJGL mode..");
app.start();
System.out.println("Waiting 5 seconds");
@ -54,7 +54,7 @@ public class TestApplication {
Thread.sleep(2000);
System.out.println("Starting in fullscreen mode");
app = new Application();
app = new LegacyApplication();
AppSettings settings = new AppSettings(true);
settings.setFullscreen(true);
settings.setResolution(-1,-1); // current width/height
@ -65,7 +65,7 @@ public class TestApplication {
Thread.sleep(2000);
System.out.println("Creating offscreen buffer application");
app = new Application();
app = new LegacyApplication();
app.start(Type.OffscreenSurface);
Thread.sleep(3000);
System.out.println("Destroying offscreen buffer");

@ -32,7 +32,7 @@
package jme3test.app;
import com.jme3.app.Application;
import com.jme3.app.LegacyApplication;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
@ -40,7 +40,7 @@ import com.jme3.scene.shape.Box;
/**
* Test a bare-bones application, without SimpleApplication.
*/
public class TestBareBonesApp extends Application {
public class TestBareBonesApp extends LegacyApplication {
private Geometry boxGeom;

@ -0,0 +1,213 @@
/*
* Copyright (c) 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 jme3test.app;
import java.lang.reflect.*;
import java.util.*;
import com.jme3.light.*;
import com.jme3.material.*;
import com.jme3.math.*;
import com.jme3.scene.*;
import com.jme3.scene.control.*;
import com.jme3.scene.shape.*;
import com.jme3.util.clone.*;
/**
*
*
* @author Paul Speed
*/
public class TestCloneSpatial {
public static void main( String... args ) throws Exception {
// Setup a test node with some children, controls, etc.
Node root = new Node("rootNode");
// A root light
DirectionalLight rootLight = new DirectionalLight();
root.addLight(rootLight);
Box sharedBox = new Box(1, 1, 1);
Geometry geom1 = new Geometry("box1", sharedBox);
Material sharedMaterial = new Material(); // not a valid material, just for testing
geom1.setMaterial(sharedMaterial);
Geometry geom2 = new Geometry("box2", sharedBox);
geom2.setMaterial(sharedMaterial);
root.attachChild(geom1);
root.attachChild(geom2);
// Add some controls
geom1.addControl(new BillboardControl());
geom2.addControl(new BillboardControl());
// A light that will only affect the children and be controlled
// by one child
PointLight childLight = new PointLight();
geom1.addLight(childLight);
geom2.addLight(childLight);
geom1.addControl(new LightControl(childLight));
// Set some shared user data also
Vector3f sharedUserData = new Vector3f(1, 2, 3);
geom1.setUserData("shared", sharedUserData);
geom2.setUserData("shared", sharedUserData);
dump("", root);
System.out.println("-------- cloning spatial --------------");
Node clone = root.clone(true);
dump("", clone);
System.out.println("-------- cloning spatial without cloning material --------------");
clone = root.clone(false);
dump("", clone);
}
/**
* Debug dump to check structure and identity
*/
public static void dump( String indent, Spatial s ) {
if( s instanceof Node ) {
dump(indent, (Node)s);
} else if( s instanceof Geometry ) {
dump(indent, (Geometry)s);
}
}
public static void dump( String indent, Node n ) {
System.out.println(indent + objectToString(n));
dumpSpatialProperties(indent + " ", n);
if( !n.getChildren().isEmpty() ) {
System.out.println(indent + " children:");
for( Spatial s : n.getChildren() ) {
dump(indent + " ", s);
}
}
}
public static void dump( String indent, Geometry g ) {
System.out.println(indent + objectToString(g));
//System.out.println(indent + " mesh:" + objectToString(g.getMesh()));
//System.out.println(indent + " material:" + objectToString(g.getMaterial()));
dumpSpatialProperties(indent + " ", g);
}
public static void dump( String indent, Control ctl ) {
System.out.println(indent + objectToString(ctl));
if( ctl instanceof AbstractControl ) {
System.out.println(indent + " spatial:" + objectToString(((AbstractControl)ctl).getSpatial()));
}
}
private static void dumpSpatialProperties( String indent, Spatial s ) {
dumpProperties(indent, s, "children");
if( !s.getUserDataKeys().isEmpty() ) {
System.out.println(indent + "userData:");
for( String key : s.getUserDataKeys() ) {
System.out.println(indent + " " + key + ":" + objectToString(s.getUserData(key)));
}
}
if( s.getNumControls() > 0 ) {
System.out.println(indent + "controls:");
for( int i = 0; i < s.getNumControls(); i++ ) {
Control ctl = s.getControl(i);
//dump(indent + " ", ctl);
dumpObject(indent + " ", ctl);
}
}
LightList lights = s.getLocalLightList();
if( lights.size() > 0 ) {
System.out.println(indent + "lights:");
for( Light l : lights ) {
dumpObject(indent + " ", l);
}
}
}
private static void dumpObject( String indent, Object o ) {
System.out.println(indent + objectToString(o));
dumpProperties(indent + " ", o);
}
private static void dumpProperties( String indent, Object o, String... skip ) {
if( o == null ) {
return;
}
Set<String> skipSet = new HashSet<>(Arrays.asList(skip));
for( Method m : o.getClass().getMethods() ) {
if( m.getParameterTypes().length > 0 ) {
continue;
}
String name = m.getName();
if( "getClass".equals(name) ) {
continue;
}
if( !name.startsWith("get") ) {
continue;
}
Class type = m.getReturnType();
if( type.isPrimitive() || type.isEnum() ) {
continue;
}
name = name.substring(3);
if( skipSet.contains(name.toLowerCase()) ) {
continue;
}
try {
Object value = m.invoke(o);
System.out.println(indent + name + ":" + objectToString(value));
} catch( Exception e ) {
throw new RuntimeException("Error with method:" + m, e);
}
}
}
private static String objectToString( Object o ) {
if( o == null ) {
return null;
}
String s = o + "@" + System.identityHashCode(o);
s = s.replaceAll("\\r?\\n", "");
return s;
}
}

@ -32,7 +32,7 @@
package jme3test.app;
import com.jme3.app.Application;
import com.jme3.app.LegacyApplication;
import com.jme3.system.AppSettings;
public class TestContextRestart {
@ -40,7 +40,7 @@ public class TestContextRestart {
public static void main(String[] args) throws InterruptedException{
AppSettings settings = new AppSettings(true);
final Application app = new Application();
final LegacyApplication app = new LegacyApplication();
app.setSettings(settings);
app.start();

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

Loading…
Cancel
Save