Merge branch 'master' into experimental

experimental
Kirill Vainer 9 years ago
commit a8fca2bcf6
  1. 138
      .gitignore
  2. 2
      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. 2
      jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java
  6. 15
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  7. 11
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  8. 43
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  9. 10
      jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java
  10. 13
      jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java
  11. 28
      jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
  12. 24
      jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java
  13. 16
      jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java
  14. 38
      jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
  15. 62
      jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
  16. 4
      jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java
  17. 32
      jme3-core/src/main/java/com/jme3/animation/AnimControl.java
  18. 31
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  19. 25
      jme3-core/src/main/java/com/jme3/animation/AudioTrack.java
  20. 3
      jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java
  21. 35
      jme3-core/src/main/java/com/jme3/animation/EffectTrack.java
  22. 13
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  23. 28
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  24. 18
      jme3-core/src/main/java/com/jme3/animation/TrackInfo.java
  25. 633
      jme3-core/src/main/java/com/jme3/app/Application.java
  26. 774
      jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
  27. 34
      jme3-core/src/main/java/com/jme3/app/SimpleApplication.java
  28. 60
      jme3-core/src/main/java/com/jme3/app/StatsAppState.java
  29. 31
      jme3-core/src/main/java/com/jme3/app/StatsView.java
  30. 26
      jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java
  31. 217
      jme3-core/src/main/java/com/jme3/audio/AudioNode.java
  32. 55
      jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
  33. 322
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  34. 35
      jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java
  35. 21
      jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java
  36. 2
      jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
  37. 3
      jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java
  38. 21
      jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java
  39. 23
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java
  40. 25
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java
  41. 22
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java
  42. 3
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java
  43. 22
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java
  44. 50
      jme3-core/src/main/java/com/jme3/font/BitmapText.java
  45. 7
      jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java
  46. 62
      jme3-core/src/main/java/com/jme3/font/Letters.java
  47. 22
      jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
  48. 56
      jme3-core/src/main/java/com/jme3/light/LightList.java
  49. 35
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  50. 58
      jme3-core/src/main/java/com/jme3/material/RenderState.java
  51. 30
      jme3-core/src/main/java/com/jme3/math/Spline.java
  52. 6
      jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
  53. 2
      jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
  54. 43
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  55. 19
      jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
  56. 108
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  57. 16
      jme3-core/src/main/java/com/jme3/scene/CameraNode.java
  58. 160
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  59. 20
      jme3-core/src/main/java/com/jme3/scene/LightNode.java
  60. 358
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  61. 157
      jme3-core/src/main/java/com/jme3/scene/Node.java
  62. 230
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  63. 18
      jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java
  64. 13
      jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java
  65. 15
      jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java
  66. 15
      jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
  67. 15
      jme3-core/src/main/java/com/jme3/scene/control/LodControl.java
  68. 14
      jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java
  69. 127
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
  70. 137
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  71. 47
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java
  72. 105
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
  73. 2
      jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java
  74. 2
      jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java
  75. 59
      jme3-core/src/main/java/com/jme3/util/IntMap.java
  76. 164
      jme3-core/src/main/java/com/jme3/util/SafeArrayList.java
  77. 81
      jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java
  78. 412
      jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
  79. 58
      jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java
  80. 99
      jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java
  81. 70
      jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java
  82. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  83. 5
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
  84. 6
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  85. 8
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  86. 18
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag
  87. 8
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
  88. 34
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
  89. 80
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag
  90. 82
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert
  91. 33
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag
  92. 10
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md
  93. 50
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag
  94. 255
      jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib
  95. 242
      jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib
  96. 1
      jme3-core/src/main/resources/com/jme3/system/.gitignore
  97. 11
      jme3-core/src/main/resources/com/jme3/system/version.properties
  98. 25
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  99. 2
      jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java
  100. 3
      jme3-core/src/test/resources/texture-parameters-newstyle.j3m
  101. Some files were not shown because too many files have changed in this diff Show More

138
.gitignore vendored

@ -1,21 +1,34 @@
*.dll
*.so
*.jnilib
*.dylib
*.iml
**/nbproject/private/
/.gradle/
/.nb-gradle/
/.nb-gradle/private/
/.nb-gradle/profiles/private/
/.idea/
/dist/
/build/
/bin/
/netbeans/
/.classpath
/.project
/.settings
/sdk/jdks/local/
/jme3-core/build/
/jme3-core/src/main/resources/com/jme3/system/version.properties
/jme3-*/build/
/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-bullet-native/bullet.zip
/jme3-bullet-native/bullet-2.82-r2704/
/jme3-android-native/openal-soft/
@ -25,24 +38,113 @@
/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/dist/
/sdk/jdks/local/
/sdk/build/
/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-*/build/
/sdk/nbi/stub/ext/components/products/jdk/build/
/sdk/nbi/stub/ext/components/products/jdk/dist/
/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
.DS_Store
/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/

@ -8,7 +8,7 @@ apply plugin: 'maven'
group = 'org.jmonkeyengine'
version = jmePomVersion
sourceCompatibility = '1.6'
sourceCompatibility = '1.7'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
repositories {

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

@ -430,7 +430,7 @@ public class AndroidTouchInput implements TouchInput {
return;
}
logger.log(Level.INFO, "event: {0}", event);
//logger.log(Level.INFO, "event: {0}", event);
inputEventQueue.add(event);
if (event instanceof TouchEvent) {

@ -215,8 +215,8 @@ public class Edge {
/**
* The method computes the crossing pint of this edge and another edge. If
* there is no crossing then null is returned. This method also allows to
* get the crossing point of the straight lines that contain these edges if
* there is no crossing then null is returned. Also null is returned if the edges are parallel.
* This method also allows to get the crossing point of the straight lines that contain these edges if
* you set the 'extend' parameter to true.
*
* @param edge
@ -227,7 +227,7 @@ public class Edge {
* @param extendSecondEdge
* set to <b>true</b> to find a crossing point along the whole
* straight that contains the given edge
* @return cross point on null if none exist
* @return cross point on null if none exist or the edges are parallel
*/
public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) {
Vector3d P1 = new Vector3d(this.getFirstVertex());
@ -235,6 +235,11 @@ public class Edge {
Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal();
Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal();
if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) {
// the edges are parallel; do not care about the crossing point
return null;
}
double t1 = 0, t2 = 0;
if(u.x == 0 && v.x == 0) {
t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y);
@ -262,11 +267,11 @@ public class Edge {
// the lines cross, check if p1 and p2 are within the edges
Vector3d p = p1.subtract(P1);
double cos = p.dot(u) / p.length();
if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= this.getLength()) {
if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) {
// p1 is inside the first edge, lets check the other edge now
p = p2.subtract(P2);
cos = p.dot(v) / p.length();
if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= edge.getLength()) {
if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) {
return p1.toVector3f();
}
}

@ -146,7 +146,6 @@ public class Face implements Comparator<Integer> {
/**
* @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
*/
@SuppressWarnings("unchecked")
public List<List<Integer>> getCurrentIndexes() {
if (triangulatedFaces == null) {
return Arrays.asList(indexes.getAll());
@ -279,16 +278,6 @@ public class Face implements Comparator<Integer> {
// two special cases will improve the computations speed
if(face.getIndexes().size() == 3) {
triangulatedFaces.add(face.getIndexes().clone());
} else if(face.getIndexes().size() == 4) {
// in case face has 4 verts we use the plain triangulation
indexes[0] = face.getIndex(0);
indexes[1] = face.getIndex(1);
indexes[2] = face.getIndex(2);
triangulatedFaces.add(new IndexesLoop(indexes));
indexes[1] = face.getIndex(2);
indexes[2] = face.getIndex(3);
triangulatedFaces.add(new IndexesLoop(indexes));
} else {
int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
while (face.vertexCount() > 0) {

@ -286,30 +286,33 @@ public class MeshHelper extends AbstractBlenderHelper {
List<Map<String, Float>> result = new ArrayList<Map<String, Float>>();
Structure parent = blenderContext.peekParent();
Structure defbase = (Structure) parent.getFieldValue("defbase");
List<String> groupNames = new ArrayList<String>();
List<Structure> defs = defbase.evaluateListBase();
for (Structure def : defs) {
groupNames.add(def.getFieldValue("name").toString());
}
if(parent != null) {
// the mesh might be saved without its parent (it is then unused)
Structure defbase = (Structure) parent.getFieldValue("defbase");
List<String> groupNames = new ArrayList<String>();
List<Structure> defs = defbase.evaluateListBase();
for (Structure def : defs) {
groupNames.add(def.getFieldValue("name").toString());
}
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData();
for (Structure dvert : dverts) {
Map<String, Float> weightsForVertex = new HashMap<String, Float>();
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if (pDW.isNotNull()) {
List<Structure> dw = pDW.fetchData();
for (Structure deformWeight : dw) {
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
String groupName = groupNames.get(groupIndex);
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData();
for (Structure dvert : dverts) {
Map<String, Float> weightsForVertex = new HashMap<String, Float>();
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if (pDW.isNotNull()) {
List<Structure> dw = pDW.fetchData();
for (Structure deformWeight : dw) {
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
String groupName = groupNames.get(groupIndex);
weightsForVertex.put(groupName, weight);
weightsForVertex.put(groupName, weight);
}
}
result.add(weightsForVertex);
}
result.add(weightsForVertex);
}
}
return result;

@ -41,6 +41,8 @@ import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -49,7 +51,7 @@ import java.io.IOException;
*
* @author normenhansen
*/
public abstract class AbstractPhysicsControl implements PhysicsControl {
public abstract class AbstractPhysicsControl implements PhysicsControl, JmeCloneable {
private final Quaternion tmp_inverseWorldRotation = new Quaternion();
protected Spatial spatial;
@ -161,6 +163,12 @@ public abstract class AbstractPhysicsControl implements PhysicsControl {
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
createSpatialData(this.spatial);
}
public void setSpatial(Spatial spatial) {
if (this.spatial != null && this.spatial != spatial) {
removeSpatialData(this.spatial);

@ -50,6 +50,8 @@ import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
@ -68,7 +70,7 @@ import java.util.logging.Logger;
*
* @author normenhansen
*/
public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener {
public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener, JmeCloneable {
protected static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName());
protected PhysicsRigidBody rigidBody;
@ -663,12 +665,21 @@ public class BetterCharacterControl extends AbstractPhysicsControl implements Ph
rigidBody.setUserObject(null);
}
@Override
public Control cloneForSpatial(Spatial spatial) {
BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
control.setJumpForce(jumpForce);
return control;
}
@Override
public Object jmeClone() {
BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
control.setJumpForce(jumpForce);
control.spatial = this.spatial;
return control;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);

@ -44,13 +44,15 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* You might want to try <code>BetterCharacterControl</code> as well.
* @author normenhansen
*/
public class CharacterControl extends PhysicsCharacter implements PhysicsControl {
public class CharacterControl extends PhysicsCharacter implements PhysicsControl, JmeCloneable {
protected Spatial spatial;
protected boolean enabled = true;
@ -87,6 +89,7 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
return spatial.getWorldTranslation();
}
@Override
public Control cloneForSpatial(Spatial spatial) {
CharacterControl control = new CharacterControl(collisionShape, stepHeight);
control.setCcdMotionThreshold(getCcdMotionThreshold());
@ -103,6 +106,29 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
return control;
}
@Override
public Object jmeClone() {
CharacterControl control = new CharacterControl(collisionShape, stepHeight);
control.setCcdMotionThreshold(getCcdMotionThreshold());
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
control.setFallSpeed(getFallSpeed());
control.setGravity(getGravity());
control.setJumpSpeed(getJumpSpeed());
control.setMaxSlope(getMaxSlope());
control.setPhysicsLocation(getPhysicsLocation());
control.setUpAxis(getUpAxis());
control.setApplyPhysicsLocal(isApplyPhysicsLocal());
control.spatial = this.spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
setUserObject(spatial);

@ -44,6 +44,8 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -51,7 +53,7 @@ import java.io.IOException;
* overlaps with other physics objects (e.g. aggro radius).
* @author normenhansen
*/
public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
public class GhostControl extends PhysicsGhostObject implements PhysicsControl, JmeCloneable {
protected Spatial spatial;
protected boolean enabled = true;
@ -93,6 +95,7 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
return spatial.getWorldRotation();
}
@Override
public Control cloneForSpatial(Spatial spatial) {
GhostControl control = new GhostControl(collisionShape);
control.setCcdMotionThreshold(getCcdMotionThreshold());
@ -105,6 +108,25 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
return control;
}
@Override
public Object jmeClone() {
GhostControl control = new GhostControl(collisionShape);
control.setCcdMotionThreshold(getCcdMotionThreshold());
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
control.setPhysicsLocation(getPhysicsLocation());
control.setPhysicsRotation(getPhysicsRotationMatrix());
control.setApplyPhysicsLocal(isApplyPhysicsLocal());
control.spatial = this.spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
setUserObject(spatial);

@ -61,6 +61,8 @@ import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
@ -92,7 +94,7 @@ import java.util.logging.Logger;
*
* @author Normen Hansen and Rémy Bouquet (Nehon)
*/
public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener {
public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable {
protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
protected List<RagdollCollisionListener> listeners;
@ -910,6 +912,7 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
public void render(RenderManager rm, ViewPort vp) {
}
@Override
public Control cloneForSpatial(Spatial spatial) {
KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold);
control.setMode(mode);
@ -919,6 +922,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
return control;
}
@Override
public Object jmeClone() {
KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold);
control.setMode(mode);
control.setRootMass(rootMass);
control.setWeightThreshold(weightThreshold);
control.setApplyPhysicsLocal(applyLocal);
control.spatial = this.spatial;
return control;
}
public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) {
Vector3f target = worldPos.subtract(targetModel.getWorldTranslation());
ikTargets.put(bone.getName(), target);

@ -51,13 +51,15 @@ import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
*
* @author normenhansen
*/
public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl {
public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl, JmeCloneable {
protected Spatial spatial;
protected boolean enabled = true;
@ -89,6 +91,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
super(shape, mass);
}
@Override
public Control cloneForSpatial(Spatial spatial) {
RigidBodyControl control = new RigidBodyControl(collisionShape, mass);
control.setAngularFactor(getAngularFactor());
@ -115,6 +118,39 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
return control;
}
@Override
public Object jmeClone() {
RigidBodyControl control = new RigidBodyControl(collisionShape, mass);
control.setAngularFactor(getAngularFactor());
control.setAngularSleepingThreshold(getAngularSleepingThreshold());
control.setCcdMotionThreshold(getCcdMotionThreshold());
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
control.setDamping(getLinearDamping(), getAngularDamping());
control.setFriction(getFriction());
control.setGravity(getGravity());
control.setKinematic(isKinematic());
control.setKinematicSpatial(isKinematicSpatial());
control.setLinearSleepingThreshold(getLinearSleepingThreshold());
control.setPhysicsLocation(getPhysicsLocation(null));
control.setPhysicsRotation(getPhysicsRotationMatrix(null));
control.setRestitution(getRestitution());
if (mass > 0) {
control.setAngularVelocity(getAngularVelocity());
control.setLinearVelocity(getLinearVelocity());
}
control.setApplyPhysicsLocal(isApplyPhysicsLocal());
control.spatial = this.spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
setUserObject(spatial);

@ -46,6 +46,8 @@ import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.Iterator;
@ -53,7 +55,7 @@ import java.util.Iterator;
*
* @author normenhansen
*/
public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
public class VehicleControl extends PhysicsVehicle implements PhysicsControl, JmeCloneable {
protected Spatial spatial;
protected boolean enabled = true;
@ -106,6 +108,7 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
return spatial.getWorldRotation();
}
@Override
public Control cloneForSpatial(Spatial spatial) {
VehicleControl control = new VehicleControl(collisionShape, mass);
control.setAngularFactor(getAngularFactor());
@ -155,6 +158,63 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
return control;
}
@Override
public Object jmeClone() {
VehicleControl control = new VehicleControl(collisionShape, mass);
control.setAngularFactor(getAngularFactor());
control.setAngularSleepingThreshold(getAngularSleepingThreshold());
control.setAngularVelocity(getAngularVelocity());
control.setCcdMotionThreshold(getCcdMotionThreshold());
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
control.setDamping(getLinearDamping(), getAngularDamping());
control.setFriction(getFriction());
control.setGravity(getGravity());
control.setKinematic(isKinematic());
control.setLinearSleepingThreshold(getLinearSleepingThreshold());
control.setLinearVelocity(getLinearVelocity());
control.setPhysicsLocation(getPhysicsLocation());
control.setPhysicsRotation(getPhysicsRotationMatrix());
control.setRestitution(getRestitution());
control.setFrictionSlip(getFrictionSlip());
control.setMaxSuspensionTravelCm(getMaxSuspensionTravelCm());
control.setSuspensionStiffness(getSuspensionStiffness());
control.setSuspensionCompression(tuning.suspensionCompression);
control.setSuspensionDamping(tuning.suspensionDamping);
control.setMaxSuspensionForce(getMaxSuspensionForce());
for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
VehicleWheel wheel = it.next();
VehicleWheel newWheel = control.addWheel(wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), wheel.isFrontWheel());
newWheel.setFrictionSlip(wheel.getFrictionSlip());
newWheel.setMaxSuspensionTravelCm(wheel.getMaxSuspensionTravelCm());
newWheel.setSuspensionStiffness(wheel.getSuspensionStiffness());
newWheel.setWheelsDampingCompression(wheel.getWheelsDampingCompression());
newWheel.setWheelsDampingRelaxation(wheel.getWheelsDampingRelaxation());
newWheel.setMaxSuspensionForce(wheel.getMaxSuspensionForce());
// Copy the wheel spatial reference directly for now. They'll
// get fixed up in the cloneFields() method
newWheel.setWheelSpatial(wheel.getWheelSpatial());
}
control.setApplyPhysicsLocal(isApplyPhysicsLocal());
control.spatial = spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
for( VehicleWheel wheel : wheels ) {
Spatial spatial = cloner.clone(wheel.getWheelSpatial());
wheel.setWheelSpatial(spatial);
}
}
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
setUserObject(spatial);

@ -70,6 +70,10 @@ public class ConeCollisionShape extends CollisionShape {
public float getRadius() {
return radius;
}
public float getHeight() {
return height;
}
public void write(JmeExporter ex) throws IOException {
super.write(ex);

@ -38,11 +38,14 @@ import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
@ -65,7 +68,7 @@ import java.util.Map.Entry;
*
* @author Kirill Vainer
*/
public final class AnimControl extends AbstractControl implements Cloneable {
public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable {
/**
* Skeleton object must contain corresponding data for the targets' weight buffers.
@ -108,6 +111,7 @@ public final class AnimControl extends AbstractControl implements Cloneable {
/**
* Internal use only.
*/
@Override
public Control cloneForSpatial(Spatial spatial) {
try {
AnimControl clone = (AnimControl) super.clone();
@ -130,6 +134,32 @@ public final class AnimControl extends AbstractControl implements Cloneable {
}
}
@Override
public Object jmeClone() {
AnimControl clone = (AnimControl) super.jmeClone();
clone.channels = new ArrayList<AnimChannel>();
clone.listeners = new ArrayList<AnimEventListener>();
return clone;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.skeleton = cloner.clone(skeleton);
// Note cloneForSpatial() never actually cloned the animation map... just its reference
HashMap<String, Animation> newMap = new HashMap<>();
// animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial
for( Map.Entry<String, Animation> e : animationMap.entrySet() ) {
newMap.put(e.getKey(), cloner.clone(e.getValue()));
}
this.animationMap = newMap;
}
/**
* @param animations Set the animations that this <code>AnimControl</code>
* will be capable of playing. The animations should be compatible

@ -35,6 +35,8 @@ import com.jme3.export.*;
import com.jme3.scene.Spatial;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -42,7 +44,7 @@ import java.io.IOException;
*
* @author Kirill Vainer, Marcin Roguski (Kaelthas)
*/
public class Animation implements Savable, Cloneable {
public class Animation implements Savable, Cloneable, JmeCloneable {
/**
* The name of the animation.
@ -190,6 +192,33 @@ public class Animation implements Savable, Cloneable {
}
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException("Error cloning", e);
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
// There is some logic here that I'm copying but I'm not sure if
// it's a mistake or not. If a track is not a CloneableTrack then it
// isn't cloned at all... even though they all implement clone() methods. -pspeed
SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class);
for( Track track : tracks ) {
if( track instanceof ClonableTrack ) {
newTracks.add(cloner.clone(track));
} else {
// this is the part that seems fishy
newTracks.add(track);
}
}
this.tracks = newTracks;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';

@ -39,6 +39,8 @@ import com.jme3.export.OutputCapsule;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -174,6 +176,7 @@ public class AudioTrack implements ClonableTrack {
* @param spatial the Spatial holding the AnimControl
* @return the cloned Track with proper reference
*/
@Override
public Track cloneForSpatial(Spatial spatial) {
AudioTrack audioTrack = new AudioTrack();
audioTrack.length = this.length;
@ -192,7 +195,27 @@ public class AudioTrack implements ClonableTrack {
return audioTrack;
}
/**
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException("Error cloning", e);
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
// Duplicating the old cloned state from cloneForSpatial()
this.initialized = false;
this.started = false;
this.played = false;
this.audio = cloner.clone(audio);
}
/**
* recursive function responsible for finding the newly cloned AudioNode
*
* @param spat

@ -32,6 +32,7 @@
package com.jme3.animation;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.JmeCloneable;
/**
* An interface that allow to clone a Track for a given Spatial.
@ -43,7 +44,7 @@ import com.jme3.scene.Spatial;
*
* @author Nehon
*/
public interface ClonableTrack extends Track {
public interface ClonableTrack extends Track, JmeCloneable {
/**
* Allows to clone the track for a given Spatial.

@ -44,6 +44,8 @@ import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -116,6 +118,17 @@ public class EffectTrack implements ClonableTrack {
}
}
@Override
public Object jmeClone() {
KillParticleControl c = new KillParticleControl();
//this control should be removed as it shouldn't have been persisted in the first place
//In the quest to find the less hackish solution to achieve this,
//making it remove itself from the spatial in the first update loop when loaded was the less bad.
c.remove = true;
c.spatial = spatial;
return c;
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
@ -125,8 +138,8 @@ public class EffectTrack implements ClonableTrack {
KillParticleControl c = new KillParticleControl();
//this control should be removed as it shouldn't have been persisted in the first place
//In the quest to find the less hackish solution to achieve this,
//making it remove itself from the spatial in the first update loop when loaded was the less bad.
//In the quest to find the less hackish solution to achieve this,
//making it remove itself from the spatial in the first update loop when loaded was the less bad.
c.remove = true;
c.setSpatial(spatial);
return c;
@ -243,7 +256,7 @@ public class EffectTrack implements ClonableTrack {
public float[] getKeyFrameTimes() {
return new float[] { startOffset };
}
/**
* Clone this track
*
@ -263,6 +276,7 @@ public class EffectTrack implements ClonableTrack {
* @param spatial the Spatial holding the AnimControl
* @return the cloned Track with proper reference
*/
@Override
public Track cloneForSpatial(Spatial spatial) {
EffectTrack effectTrack = new EffectTrack();
effectTrack.particlesPerSeconds = this.particlesPerSeconds;
@ -283,6 +297,21 @@ public class EffectTrack implements ClonableTrack {
return effectTrack;
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException("Error cloning", e);
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.emitter = cloner.clone(emitter);
}
/**
* recursive function responsible for finding the newly cloned Emitter
*

@ -34,6 +34,8 @@ package com.jme3.animation;
import com.jme3.export.*;
import com.jme3.math.Matrix4f;
import com.jme3.util.TempVars;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -45,7 +47,7 @@ import java.util.List;
*
* @author Kirill Vainer
*/
public final class Skeleton implements Savable {
public final class Skeleton implements Savable, JmeCloneable {
private Bone[] rootBones;
private Bone[] boneList;
@ -118,6 +120,15 @@ public final class Skeleton implements Savable {
public Skeleton() {
}
@Override
public Object jmeClone() {
return new Skeleton(this);
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
}
private void createSkinningMatrices() {
skinningMatrixes = new Matrix4f[boneList.length];
for (int i = 0; i < skinningMatrixes.length; i++) {

@ -46,6 +46,8 @@ import com.jme3.scene.control.Control;
import com.jme3.shader.VarType;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
@ -63,7 +65,7 @@ import java.util.logging.Logger;
*
* @author Rémy Bouquet Based on AnimControl by Kirill Vainer
*/
public class SkeletonControl extends AbstractControl implements Cloneable {
public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable {
/**
* The skeleton of the model.
@ -348,6 +350,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
}
}
@Override
public Control cloneForSpatial(Spatial spatial) {
Node clonedNode = (Node) spatial;
SkeletonControl clone = new SkeletonControl();
@ -388,6 +391,29 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
return clone;
}
@Override
public Object jmeClone() {
return super.jmeClone();
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
this.skeleton = cloner.clone(skeleton);
// If the targets were cloned then this will clone them. If the targets
// were shared then this will share them.
this.targets = cloner.clone(targets);
// Not automatic set cloning yet
Set<Material> newMaterials = new HashSet<Material>();
for( Material m : this.materials ) {
newMaterials.add(cloner.clone(m));
}
this.materials = newMaterials;
}
/**
*
* @param boneName the name of the bone

@ -36,6 +36,8 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.ArrayList;
@ -48,7 +50,7 @@ import java.util.ArrayList;
*
* @author Nehon
*/
public class TrackInfo implements Savable {
public class TrackInfo implements Savable, JmeCloneable {
ArrayList<Track> tracks = new ArrayList<Track>();
@ -72,4 +74,18 @@ public class TrackInfo implements Savable {
public void addTrack(Track track) {
tracks.add(track);
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException("Error cloning", e);
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.tracks = cloner.clone(tracks);
}
}

@ -33,112 +33,53 @@ 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.
*
* By default, the application will
* {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop}
*
* 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;
}
public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior);
/**
* Returns true if pause on lost focus is enabled, false otherwise.
*
* @return true if pause on lost focus is enabled
*
* @see #getLostFocusBehavior()
* @see #getLostFocusBehavior()
*/
public boolean isPauseOnLostFocus() {
return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus;
}
public boolean isPauseOnLostFocus();
/**
* Enable or disable pause on lost focus.
@ -153,52 +94,10 @@ public class Application implements SystemListener {
*
* @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 = 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,332 +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();
}
public void setTimer(Timer timer);
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());
}
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;
}
public JmeContext getContext();
/**
* @return The {@link Camera camera} for the application
* @return The main {@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);
}
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);
}
}
/**
* Returns the current AppProfiler hook, or null if none is set.
*/
public AppProfiler getAppProfiler() {
return prof;
}
public void setAppProfiler(AppProfiler 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);
}
/**
* Determine if the application has already started.
*
* After {@link #start() } but before {@link #stop() }.
*
* @return if started
*/
public boolean isStarted() {
return context != null && context.isCreated();
}
/**
* Internal use only.
* Returns the current AppProfiler hook, or null if none is set.
*/
public void reshape(int w, int h){
renderManager.notifyReshape(w, h);
}
public AppProfiler getAppProfiler();
/**
* Restarts the context, applying any changed settings.
@ -544,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
@ -557,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
@ -661,15 +220,11 @@ public class Application implements SystemListener {
* 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;
}
public <V> Future<V> enqueue(Callable<V> callable);
/**
* Enqueues a runnable object to execute in the jME3
* rendering thread.
@ -677,110 +232,16 @@ public class Application implements SystemListener {
* 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();
context = null;
}
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;
}
public ViewPort getGuiViewPort();
@Override
public Object call(){
runnable.run();
return null;
}
}
public ViewPort getViewPort();
}

@ -0,0 +1,774 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.app;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioContext;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.Listener;
import com.jme3.input.*;
import com.jme3.math.Vector3f;
import com.jme3.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(){
initStateManager();
}
/**
* 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){
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;
}
}
}

@ -59,17 +59,17 @@ import com.jme3.system.JmeSystem;
* <tr><td>C</td><td>- Display the camera position and rotation in the console.</td></tr>
* <tr><td>M</td><td>- Display memory usage in the console.</td></tr>
* </table>
*
*
* 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;
public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats";
protected Node rootNode = new Node("Root Node");
protected Node guiNode = new Node("Gui Node");
protected BitmapText fpsText;
@ -77,7 +77,7 @@ public abstract class SimpleApplication extends Application {
protected FlyByCamera flyCam;
protected boolean showSettings = true;
private AppActionListener actionListener = new AppActionListener();
private class AppActionListener implements ActionListener {
public void onAction(String name, boolean value, float tpf) {
@ -101,7 +101,7 @@ public abstract class SimpleApplication extends Application {
public SimpleApplication( AppState... initialStates ) {
super();
if (initialStates != null) {
for (AppState a : initialStates) {
if (a != null) {
@ -193,7 +193,7 @@ public abstract class SimpleApplication extends Application {
guiViewPort.attachScene(guiNode);
if (inputManager != null) {
// We have to special-case the FlyCamAppState because too
// many SimpleApplication subclasses expect it to exist in
// simpleInit(). But at least it only gets initialized if
@ -201,7 +201,7 @@ public abstract class SimpleApplication extends Application {
if (stateManager.getState(FlyCamAppState.class) != null) {
flyCam = new FlyByCamera(cam);
flyCam.setMoveSpeed(1f); // odd to set this here but it did it before
stateManager.getState(FlyCamAppState.class).setCamera( flyCam );
stateManager.getState(FlyCamAppState.class).setCamera( flyCam );
}
if (context.getType() == Type.Display) {
@ -210,10 +210,10 @@ public abstract class SimpleApplication extends Application {
if (stateManager.getState(StatsAppState.class) != null) {
inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
}
inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
}
if (stateManager.getState(StatsAppState.class) != null) {
@ -230,37 +230,37 @@ public abstract class SimpleApplication extends Application {
@Override
public void update() {
if (prof!=null) prof.appStep(AppStep.BeginFrame);
super.update(); // makes sure to execute AppTasks
if (speed == 0 || paused) {
return;
}
float tpf = timer.getTimePerFrame() * speed;
// update states
if (prof!=null) prof.appStep(AppStep.StateManagerUpdate);
stateManager.update(tpf);
// simple update and root node
simpleUpdate(tpf);
if (prof!=null) prof.appStep(AppStep.SpatialUpdate);
rootNode.updateLogicalState(tpf);
guiNode.updateLogicalState(tpf);
rootNode.updateGeometricState();
guiNode.updateGeometricState();
// render states
if (prof!=null) prof.appStep(AppStep.StateManagerRender);
stateManager.render(renderManager);
if (prof!=null) prof.appStep(AppStep.RenderFrame);
renderManager.render(tpf, context.isRenderable());
simpleRender(renderManager);
stateManager.postRender();
if (prof!=null) prof.appStep(AppStep.EndFrame);
}

@ -46,7 +46,7 @@ import com.jme3.scene.shape.Quad;
/**
* Displays stats in SimpleApplication's GUI node or
* using the node and font parameters provided.
* using the node and font parameters provided.
*
* @author Paul Speed
*/
@ -58,7 +58,7 @@ public class StatsAppState extends AbstractAppState {
private boolean showFps = true;
private boolean showStats = true;
private boolean darkenBehind = true;
protected Node guiNode;
protected float secondCounter = 0.0f;
protected int frameCounter = 0;
@ -68,7 +68,7 @@ public class StatsAppState extends AbstractAppState {
protected Geometry darkenStats;
public StatsAppState() {
}
}
public StatsAppState( Node guiNode, BitmapFont guiFont ) {
this.guiNode = guiNode;
@ -89,7 +89,7 @@ public class StatsAppState extends AbstractAppState {
public BitmapText getFpsText() {
return fpsText;
}
public StatsView getStatsView() {
return statsView;
}
@ -110,7 +110,7 @@ public class StatsAppState extends AbstractAppState {
if (darkenFps != null) {
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
}
}
}
@ -138,7 +138,7 @@ public class StatsAppState extends AbstractAppState {
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = app;
if (app instanceof SimpleApplication) {
SimpleApplication simpleApp = (SimpleApplication)app;
if (guiNode == null) {
@ -147,21 +147,21 @@ public class StatsAppState extends AbstractAppState {
if (guiFont == null ) {
guiFont = simpleApp.guiFont;
}
}
}
if (guiNode == null) {
throw new RuntimeException( "No guiNode specific and cannot be automatically determined." );
}
}
if (guiFont == null) {
guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
}
loadFpsText();
loadStatsView();
loadFpsText();
loadStatsView();
loadDarken();
}
/**
* Attaches FPS statistics to guiNode and displays it on the screen.
*
@ -170,12 +170,12 @@ public class StatsAppState extends AbstractAppState {
if (fpsText == null) {
fpsText = new BitmapText(guiFont, false);
}
fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
fpsText.setText("Frames per second");
fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
guiNode.attachChild(fpsText);
}
/**
@ -184,53 +184,53 @@ public class StatsAppState extends AbstractAppState {
*
*/
public void loadStatsView() {
statsView = new StatsView("Statistics View",
app.getAssetManager(),
statsView = new StatsView("Statistics View",
app.getAssetManager(),
app.getRenderer().getStatistics());
// move it up so it appears above fps text
statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);
statsView.setEnabled(showStats);
statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
guiNode.attachChild(statsView);
}
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);
darkenFps = new Geometry("StatsDarken", new Quad(200, fpsText.getLineHeight()));
darkenFps.setMaterial(mat);
darkenFps.setLocalTranslation(0, 0, -1);
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
guiNode.attachChild(darkenFps);
darkenStats = new Geometry("StatsDarken", new Quad(200, statsView.getHeight()));
darkenStats.setMaterial(mat);
darkenStats.setLocalTranslation(0, fpsText.getHeight(), -1);
darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always);
guiNode.attachChild(darkenStats);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (enabled) {
fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
statsView.setEnabled(showStats);
statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always);
} else {
fpsText.setCullHint(CullHint.Always);
darkenFps.setCullHint(CullHint.Always);
statsView.setEnabled(false);
statsView.setCullHint(CullHint.Always);
statsView.setCullHint(CullHint.Always);
darkenStats.setCullHint(CullHint.Always);
}
}
@Override
public void update(float tpf) {
if (showFps) {
@ -241,14 +241,14 @@ public class StatsAppState extends AbstractAppState {
fpsText.setText("Frames per second: " + fps);
secondCounter = 0.0f;
frameCounter = 0;
}
}
}
}
@Override
public void cleanup() {
super.cleanup();
guiNode.detachChild(statsView);
guiNode.detachChild(fpsText);
guiNode.detachChild(darkenFps);

@ -41,6 +41,8 @@ import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
/**
* The <code>StatsView</code> provides a heads-up display (HUD) of various
@ -58,7 +60,7 @@ import com.jme3.scene.control.Control;
* rootNode.attachChild(statsView);<br/>
* </code>
*/
public class StatsView extends Node implements Control {
public class StatsView extends Node implements Control, JmeCloneable {
private BitmapText statText;
private Statistics statistics;
@ -67,7 +69,7 @@ public class StatsView extends Node implements Control {
private int[] statData;
private boolean enabled = true;
private final StringBuilder stringBuilder = new StringBuilder();
public StatsView(String name, AssetManager manager, Statistics stats){
@ -93,32 +95,43 @@ public class StatsView extends Node implements Control {
public float getHeight() {
return statText.getLineHeight() * statLabels.length;
}
public void update(float tpf) {
if (!isEnabled())
if (!isEnabled())
return;
statistics.getData(statData);
stringBuilder.setLength(0);
// Need to walk through it backwards, as the first label
// Need to walk through it backwards, as the first label
// should appear at the bottom, not the top.
for (int i = statLabels.length - 1; i >= 0; i--) {
stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n');
}
statText.setText(stringBuilder);
// Moved to ResetStatsState to make sure it is
// done even if there is no StatsView or the StatsView
// is disable.
//statistics.clearFrame();
}
@Override
public Control cloneForSpatial(Spatial spatial) {
return (Control) spatial;
}
@Override
public StatsView jmeClone() {
throw new UnsupportedOperationException("Not yet implemented.");
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
throw new UnsupportedOperationException("Not yet implemented.");
}
public void setSpatial(Spatial spatial) {
}

@ -249,21 +249,23 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen
}
logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath());
OutputStream outStream = null;
try {
outStream = new FileOutputStream(file);
JmeSystem.writeImageFile(outStream, "png", outBuf, width, height);
writeImageFile(file);
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error while saving screenshot", ex);
} finally {
if (outStream != null){
try {
outStream.close();
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error while saving screenshot", ex);
}
}
}
}
}
}
/**
* Called by postFrame() once the screen has been captured to outBuf.
*/
protected void writeImageFile( File file ) throws IOException {
OutputStream outStream = new FileOutputStream(file);
try {
JmeSystem.writeImageFile(outStream, "png", outBuf, width, height);
} finally {
outStream.close();
}
}
}

@ -41,26 +41,27 @@ 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;
/**
* An <code>AudioNode</code> is a scene Node which can play audio assets.
*
* An AudioNode is either positional or ambient, with positional being the
* default. Once a positional node is attached to the scene, its location and
* velocity relative to the {@link Listener} affect how it sounds when played.
* Positional nodes can only play monoaural (single-channel) assets, not stereo
* ones.
*
* An ambient AudioNode plays in "headspace", meaning that the node's location
* and velocity do not affect how it sounds when played. Ambient audio nodes can
* play stereo assets.
*
* The "positional" property of an AudioNode can be set via
* An <code>AudioNode</code> is a scene Node which can play audio assets.
*
* An AudioNode is either positional or ambient, with positional being the
* default. Once a positional node is attached to the scene, its location and
* velocity relative to the {@link Listener} affect how it sounds when played.
* Positional nodes can only play monoaural (single-channel) assets, not stereo
* ones.
*
* An ambient AudioNode plays in "headspace", meaning that the node's location
* and velocity do not affect how it sounds when played. Ambient audio nodes can
* play stereo assets.
*
* The "positional" property of an AudioNode can be set via
* {@link AudioNode#setPositional(boolean) }.
*
*
* @author normenhansen
* @author Kirill Vainer
*/
@ -99,15 +100,15 @@ public class AudioNode extends Node implements AudioSource {
* {@link AudioNode#play() } is called.
*/
Playing,
/**
* The audio node is currently paused.
*/
Paused,
/**
* The audio node is currently stopped.
* This will be set if {@link AudioNode#stop() } is called
* This will be set if {@link AudioNode#stop() } is called
* or the audio has reached the end of the file.
*/
Stopped,
@ -121,14 +122,14 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new <code>AudioNode</code> with the given data and key.
*
*
* @param audioData The audio data contains the audio track to play.
* @param audioKey The audio key that was used to load the AudioData
*/
public AudioNode(AudioData audioData, AudioKey audioKey) {
setAudioData(audioData, audioKey);
}
/**
* Creates a new <code>AudioNode</code> with the given audio file.
* @param assetManager The asset manager to use to load the audio file
@ -142,16 +143,16 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new <code>AudioNode</code> with the given audio file.
*
*
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
* @param stream If true, the audio will be streamed gradually from disk,
* @param stream If true, the audio will be streamed gradually from disk,
* otherwise, it will be buffered.
* @param streamCache If stream is also true, then this specifies if
* the stream cache is used. When enabled, the audio stream will
* be read entirely but not decoded, allowing features such as
* be read entirely but not decoded, allowing features such as
* seeking, looping and determining duration.
*
*
* @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead
*/
public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
@ -161,12 +162,12 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new <code>AudioNode</code> with the given audio file.
*
*
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
* @param stream If true, the audio will be streamed gradually from disk,
* @param stream If true, the audio will be streamed gradually from disk,
* otherwise, it will be buffered.
*
*
* @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead
*/
public AudioNode(AssetManager assetManager, String name, boolean stream) {
@ -175,20 +176,20 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new <code>AudioNode</code> with the given audio file.
*
*
* @param audioRenderer The audio renderer to use for playing. Cannot be null.
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
*
*
* @deprecated AudioRenderer parameter is ignored.
*/
public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
this(assetManager, name, DataType.Buffer);
}
/**
* Creates a new <code>AudioNode</code> with the given audio file.
*
*
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
* @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType) } instead
@ -196,14 +197,14 @@ public class AudioNode extends Node implements AudioSource {
public AudioNode(AssetManager assetManager, String name) {
this(assetManager, name, DataType.Buffer);
}
protected AudioRenderer getRenderer() {
AudioRenderer result = AudioContext.getAudioRenderer();
if( result == null )
throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." );
return result;
return result;
}
/**
* Start playing the audio.
*/
@ -217,7 +218,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Start playing an instance of this audio. This method can be used
* to play the same <code>AudioNode</code> multiple times. Note
* that changes to the parameters of this AudioNode will not effect the
* that changes to the parameters of this AudioNode will not effect the
* instances already playing.
*/
public void playInstance(){
@ -226,21 +227,21 @@ public class AudioNode extends Node implements AudioSource {
}
getRenderer().playSourceInstance(this);
}
/**
* Stop playing the audio that was started with {@link AudioNode#play() }.
*/
public void stop(){
getRenderer().stopSource(this);
}
/**
* Pause the audio that was started with {@link AudioNode#play() }.
*/
public void pause(){
getRenderer().pauseSource(this);
}
/**
* Do not use.
*/
@ -261,7 +262,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The {#link Filter dry filter} that is set.
* @see AudioNode#setDryFilter(com.jme3.audio.Filter)
* @see AudioNode#setDryFilter(com.jme3.audio.Filter)
*/
public Filter getDryFilter() {
return dryFilter;
@ -269,14 +270,14 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the dry filter to use for this audio node.
*
* When {@link AudioNode#setReverbEnabled(boolean) reverb} is used,
* the dry filter will only influence the "dry" portion of the audio,
*
* When {@link AudioNode#setReverbEnabled(boolean) reverb} is used,
* the dry filter will only influence the "dry" portion of the audio,
* e.g. not the reverberated parts of the AudioNode playing.
*
*
* See the relevent documentation for the {@link Filter} to determine
* the effect.
*
*
* @param dryFilter The filter to set, or null to disable dry filter.
*/
public void setDryFilter(Filter dryFilter) {
@ -289,7 +290,7 @@ public class AudioNode extends Node implements AudioSource {
* Set the audio data to use for the audio. Note that this method
* can only be called once, if for example the audio node was initialized
* without an {@link AudioData}.
*
*
* @param audioData The audio data contains the audio track to play.
* @param audioKey The audio key that was used to load the AudioData
*/
@ -303,7 +304,7 @@ public class AudioNode extends Node implements AudioSource {
}
/**
* @return The {@link AudioData} set previously with
* @return The {@link AudioData} set previously with
* {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) }
* or any of the constructors that initialize the audio data.
*/
@ -312,7 +313,7 @@ public class AudioNode extends Node implements AudioSource {
}
/**
* @return The {@link Status} of the audio node.
* @return The {@link Status} of the audio node.
* The status will be changed when either the {@link AudioNode#play() }
* or {@link AudioNode#stop() } methods are called.
*/
@ -339,7 +340,7 @@ public class AudioNode extends Node implements AudioSource {
else
return data.getDataType();
}
/**
* @return True if the audio will keep looping after it is done playing,
* otherwise, false.
@ -351,7 +352,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the looping mode for the audio node. The default is false.
*
*
* @param loop True if the audio should keep looping after it is done playing.
*/
public void setLooping(boolean loop) {
@ -362,8 +363,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The pitch of the audio, also the speed of playback.
*
* @see AudioNode#setPitch(float)
*
* @see AudioNode#setPitch(float)
*/
public float getPitch() {
return pitch;
@ -372,7 +373,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the pitch of the audio, also the speed of playback.
* The value must be between 0.5 and 2.0.
*
*
* @param pitch The pitch to set.
* @throws IllegalArgumentException If pitch is not between 0.5 and 2.0.
*/
@ -388,7 +389,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The volume of this audio node.
*
*
* @see AudioNode#setVolume(float)
*/
public float getVolume() {
@ -397,9 +398,9 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the volume of this audio node.
*
*
* The volume is specified as gain. 1.0 is the default.
*
*
* @param volume The volume to set.
* @throws IllegalArgumentException If volume is negative
*/
@ -422,7 +423,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the time offset in the sound sample when to start playing.
*
*
* @param timeOffset The time offset
* @throws IllegalArgumentException If timeOffset is negative
*/
@ -439,7 +440,7 @@ public class AudioNode extends Node implements AudioSource {
play();
}
}
@Override
public float getPlaybackTime() {
if (channel >= 0)
@ -451,10 +452,10 @@ public class AudioNode extends Node implements AudioSource {
public Vector3f getPosition() {
return getWorldTranslation();
}
/**
* @return The velocity of the audio node.
*
*
* @see AudioNode#setVelocity(com.jme3.math.Vector3f)
*/
public Vector3f getVelocity() {
@ -464,7 +465,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the velocity of the audio node. The velocity is expected
* to be in meters. Does nothing if the audio node is not positional.
*
*
* @param velocity The velocity to set.
* @see AudioNode#setPositional(boolean)
*/
@ -476,7 +477,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return True if reverb is enabled, otherwise false.
*
*
* @see AudioNode#setReverbEnabled(boolean)
*/
public boolean isReverbEnabled() {
@ -487,10 +488,10 @@ public class AudioNode extends Node implements AudioSource {
* Set to true to enable reverberation effects for this audio node.
* Does nothing if the audio node is not positional.
* <br/>
* When enabled, the audio environment set with
* When enabled, the audio environment set with
* {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) }
* will apply a reverb effect to the audio playing from this audio node.
*
*
* @param reverbEnabled True to enable reverb.
*/
public void setReverbEnabled(boolean reverbEnabled) {
@ -502,8 +503,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return Filter for the reverberations of this audio node.
*
* @see AudioNode#setReverbFilter(com.jme3.audio.Filter)
*
* @see AudioNode#setReverbFilter(com.jme3.audio.Filter)
*/
public Filter getReverbFilter() {
return reverbFilter;
@ -515,7 +516,7 @@ public class AudioNode extends Node implements AudioSource {
* The reverb filter will influence the reverberations
* of the audio node playing. This only has an effect if
* reverb is enabled.
*
*
* @param reverbFilter The reverb filter to set.
* @see AudioNode#setDryFilter(com.jme3.audio.Filter)
*/
@ -527,7 +528,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return Max distance for this audio node.
*
*
* @see AudioNode#setMaxDistance(float)
*/
public float getMaxDistance() {
@ -545,7 +546,7 @@ public class AudioNode extends Node implements AudioSource {
* get any quieter than at that distance. If you want a sound to fall-off
* very quickly then set ref distance very short and leave this distance
* very long.
*
*
* @param maxDistance The maximum playing distance.
* @throws IllegalArgumentException If maxDistance is negative
*/
@ -561,8 +562,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The reference playing distance for the audio node.
*
* @see AudioNode#setRefDistance(float)
*
* @see AudioNode#setRefDistance(float)
*/
public float getRefDistance() {
return refDistance;
@ -574,7 +575,7 @@ public class AudioNode extends Node implements AudioSource {
* <br/>
* The reference playing distance is the distance at which the
* audio node will be exactly half of its volume.
*
*
* @param refDistance The reference playing distance.
* @throws IllegalArgumentException If refDistance is negative
*/
@ -590,8 +591,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return True if the audio node is directional
*
* @see AudioNode#setDirectional(boolean)
*
* @see AudioNode#setDirectional(boolean)
*/
public boolean isDirectional() {
return directional;
@ -601,10 +602,10 @@ public class AudioNode extends Node implements AudioSource {
* Set the audio node to be directional.
* Does nothing if the audio node is not positional.
* <br/>
* After setting directional, you should call
* After setting directional, you should call
* {@link AudioNode#setDirection(com.jme3.math.Vector3f) }
* to set the audio node's direction.
*
*
* @param directional If the audio node is directional
*/
public void setDirectional(boolean directional) {
@ -615,7 +616,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The direction of this audio node.
*
*
* @see AudioNode#setDirection(com.jme3.math.Vector3f)
*/
public Vector3f getDirection() {
@ -625,9 +626,9 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the direction of this audio node.
* Does nothing if the audio node is not directional.
*
* @param direction
* @see AudioNode#setDirectional(boolean)
*
* @param direction
* @see AudioNode#setDirectional(boolean)
*/
public void setDirection(Vector3f direction) {
this.direction = direction;
@ -637,8 +638,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The directional audio node, cone inner angle.
*
* @see AudioNode#setInnerAngle(float)
*
* @see AudioNode#setInnerAngle(float)
*/
public float getInnerAngle() {
return innerAngle;
@ -647,7 +648,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the directional audio node cone inner angle.
* Does nothing if the audio node is not directional.
*
*
* @param innerAngle The cone inner angle.
*/
public void setInnerAngle(float innerAngle) {
@ -658,8 +659,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The directional audio node, cone outer angle.
*
* @see AudioNode#setOuterAngle(float)
*
* @see AudioNode#setOuterAngle(float)
*/
public float getOuterAngle() {
return outerAngle;
@ -668,7 +669,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the directional audio node cone outer angle.
* Does nothing if the audio node is not directional.
*
*
* @param outerAngle The cone outer angle.
*/
public void setOuterAngle(float outerAngle) {
@ -679,8 +680,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return True if the audio node is positional.
*
* @see AudioNode#setPositional(boolean)
*
* @see AudioNode#setPositional(boolean)
*/
public boolean isPositional() {
return positional;
@ -690,7 +691,7 @@ public class AudioNode extends Node implements AudioSource {
* Set the audio node as positional.
* The position, velocity, and distance parameters effect positional
* audio nodes. Set to false if the audio node should play in "headspace".
*
*
* @param positional True if the audio node should be positional, otherwise
* false if it should be headspace.
*/
@ -707,7 +708,7 @@ public class AudioNode extends Node implements AudioSource {
if ((refreshFlags & RF_TRANSFORM) != 0){
updatePos = true;
}
super.updateGeometricState();
if (updatePos && channel >= 0)
@ -717,13 +718,37 @@ public class AudioNode extends Node implements AudioSource {
@Override
public AudioNode clone(){
AudioNode clone = (AudioNode) super.clone();
clone.direction = direction.clone();
clone.velocity = velocity.clone();
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);
@ -745,7 +770,7 @@ public class AudioNode extends Node implements AudioSource {
oc.write(direction, "direction", null);
oc.write(innerAngle, "inner_angle", 360);
oc.write(outerAngle, "outer_angle", 360);
oc.write(positional, "positional", false);
}
@ -753,7 +778,7 @@ public class AudioNode extends Node implements AudioSource {
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
// NOTE: In previous versions of jME3, audioKey was actually
// written with the name "key". This has been changed
// to "audio_key" in case Spatial's key will be written as "key".
@ -762,7 +787,7 @@ public class AudioNode extends Node implements AudioSource {
}else{
audioKey = (AudioKey) ic.readSavable("audio_key", null);
}
loop = ic.readBoolean("looping", false);
volume = ic.readFloat("volume", 1);
pitch = ic.readFloat("pitch", 1);
@ -779,9 +804,9 @@ public class AudioNode extends Node implements AudioSource {
direction = (Vector3f) ic.readSavable("direction", null);
innerAngle = ic.readFloat("inner_angle", 360);
outerAngle = ic.readFloat("outer_angle", 360);
positional = ic.readBoolean("positional", false);
if (audioKey != null) {
try {
data = im.getAssetManager().loadAsset(audioKey);

@ -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
@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -56,15 +58,15 @@ import java.io.IOException;
*
* @author Nehon
*/
public class MotionEvent extends AbstractCinematicEvent implements Control {
public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable {
protected Spatial spatial;
protected int currentWayPoint;
protected float currentValue;
protected Vector3f direction = new Vector3f();
protected Vector3f lookAt;
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;
@ -118,7 +120,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path) {
super();
this.spatial = spatial;
spatial.addControl(this);
this.path = path;
}
@ -130,7 +131,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) {
super(initialDuration);
this.spatial = spatial;
spatial.addControl(this);
this.path = path;
}
@ -142,7 +142,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) {
super();
this.spatial = spatial;
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
@ -155,7 +154,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
super(initialDuration);
this.spatial = spatial;
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
@ -211,9 +209,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
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);
}
@ -222,9 +220,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
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);
}
@ -274,15 +272,17 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
* @param spatial
* @return
*/
@Override
public Control cloneForSpatial(Spatial spatial) {
MotionEvent control = new MotionEvent(spatial, path);
MotionEvent control = new MotionEvent();
control.setPath(path);
control.playState = playState;
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;
@ -291,6 +291,31 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
return control;
}
@Override
public Object jmeClone() {
MotionEvent control = new MotionEvent();
control.path = path;
control.playState = playState;
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
control.lookAt = lookAt;
control.upVector = upVector.clone();
control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
control.directionType = directionType;
control.spatial = spatial;
return control;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
@Override
public void onPlay() {
traveledDistance = 0;

@ -54,6 +54,8 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -63,12 +65,12 @@ import java.io.IOException;
* Particle emitters can be used to simulate various kinds of phenomena,
* such as fire, smoke, explosions and much more.
* <p>
* Particle emitters have many properties which are used to control the
* simulation. The interpretation of these properties depends on the
* Particle emitters have many properties which are used to control the
* simulation. The interpretation of these properties depends on the
* {@link ParticleInfluencer} that has been assigned to the emitter via
* {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }.
* By default the implementation {@link DefaultParticleInfluencer} is used.
*
*
* @author Kirill Vainer
*/
public class ParticleEmitter extends Geometry {
@ -98,7 +100,7 @@ public class ParticleEmitter extends Geometry {
private Vector3f faceNormal = new Vector3f(Vector3f.NAN);
private int imagesX = 1;
private int imagesY = 1;
private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f);
private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f);
private float startSize = 0.2f;
@ -108,7 +110,7 @@ public class ParticleEmitter extends Geometry {
private transient Vector3f temp = new Vector3f();
private transient Vector3f lastPos;
public static class ParticleEmitterControl implements Control {
public static class ParticleEmitterControl implements Control, JmeCloneable {
ParticleEmitter parentEmitter;
@ -119,11 +121,26 @@ public class ParticleEmitter extends Geometry {
this.parentEmitter = parentEmitter;
}
@Override
public Control cloneForSpatial(Spatial spatial) {
return this; // WARNING: Sets wrong control on spatial. Will be
// fixed automatically by ParticleEmitter.clone() method.
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException("Error cloning", e);
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.parentEmitter = cloner.clone(parentEmitter);
}
public void setSpatial(Spatial spatial) {
}
@ -157,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();
@ -194,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);
@ -208,7 +270,7 @@ public class ParticleEmitter extends Geometry {
meshType = type;
// Must create clone of shape/influencer so that a reference to a static is
// Must create clone of shape/influencer so that a reference to a static is
// not maintained
shape = shape.deepClone();
particleInfluencer = particleInfluencer.clone();
@ -250,10 +312,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the {@link ParticleInfluencer} to influence this particle emitter.
*
* @param particleInfluencer the {@link ParticleInfluencer} to influence
*
* @param particleInfluencer the {@link ParticleInfluencer} to influence
* this particle emitter.
*
*
* @see ParticleInfluencer
*/
public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
@ -261,12 +323,12 @@ public class ParticleEmitter extends Geometry {
}
/**
* Returns the {@link ParticleInfluencer} that influences this
* Returns the {@link ParticleInfluencer} that influences this
* particle emitter.
*
* @return the {@link ParticleInfluencer} that influences this
*
* @return the {@link ParticleInfluencer} that influences this
* particle emitter.
*
*
* @see ParticleInfluencer
*/
public ParticleInfluencer getParticleInfluencer() {
@ -275,12 +337,12 @@ public class ParticleEmitter extends Geometry {
/**
* Returns the mesh type used by the particle emitter.
*
*
*
*
* @return the mesh type used by the particle emitter.
*
*
* @see #setMeshType(com.jme3.effect.ParticleMesh.Type)
* @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
* @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
*/
public ParticleMesh.Type getMeshType() {
return meshType;
@ -308,26 +370,26 @@ public class ParticleEmitter extends Geometry {
}
/**
* Returns true if particles should spawn in world space.
*
* @return true if particles should spawn in world space.
*
* @see ParticleEmitter#setInWorldSpace(boolean)
* Returns true if particles should spawn in world space.
*
* @return true if particles should spawn in world space.
*
* @see ParticleEmitter#setInWorldSpace(boolean)
*/
public boolean isInWorldSpace() {
return worldSpace;
}
/**
* Set to true if particles should spawn in world space.
*
* Set to true if particles should spawn in world space.
*
* <p>If set to true and the particle emitter is moved in the scene,
* then particles that have already spawned won't be effected by this
* motion. If set to false, the particles will emit in local space
* and when the emitter is moved, so are all the particles that
* were emitted previously.
*
* @param worldSpace true if particles should spawn in world space.
*
* @param worldSpace true if particles should spawn in world space.
*/
public void setInWorldSpace(boolean worldSpace) {
this.setIgnoreTransform(worldSpace);
@ -336,7 +398,7 @@ public class ParticleEmitter extends Geometry {
/**
* Returns the number of visible particles (spawned but not dead).
*
*
* @return the number of visible particles
*/
public int getNumVisibleParticles() {
@ -348,7 +410,7 @@ public class ParticleEmitter extends Geometry {
* Set the maximum amount of particles that
* can exist at the same time with this emitter.
* Calling this method many times is not recommended.
*
*
* @param numParticles the maximum amount of particles that
* can exist at the same time with this emitter.
*/
@ -370,13 +432,13 @@ public class ParticleEmitter extends Geometry {
/**
* Returns a list of all particles (shouldn't be used in most cases).
*
*
* <p>
* This includes both existing and non-existing particles.
* The size of the array is set to the <code>numParticles</code> value
* specified in the constructor or {@link ParticleEmitter#setNumParticles(int) }
* method.
*
* method.
*
* @return a list of all particles.
*/
public Particle[] getParticles() {
@ -384,11 +446,11 @@ public class ParticleEmitter extends Geometry {
}
/**
* Get the normal which particles are facing.
*
* @return the normal which particles are facing.
*
* @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
* Get the normal which particles are facing.
*
* @return the normal which particles are facing.
*
* @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
*/
public Vector3f getFaceNormal() {
if (Vector3f.isValidVector(faceNormal)) {
@ -399,8 +461,8 @@ public class ParticleEmitter extends Geometry {
}
/**
* Sets the normal which particles are facing.
*
* Sets the normal which particles are facing.
*
* <p>By default, particles
* will face the camera, but for some effects (e.g shockwave) it may
* be necessary to face a specific direction instead. To restore
@ -420,10 +482,10 @@ public class ParticleEmitter extends Geometry {
/**
* Returns the rotation speed in radians/sec for particles.
*
*
* @return the rotation speed in radians/sec for particles.
*
* @see ParticleEmitter#setRotateSpeed(float)
*
* @see ParticleEmitter#setRotateSpeed(float)
*/
public float getRotateSpeed() {
return rotateSpeed;
@ -432,7 +494,7 @@ public class ParticleEmitter extends Geometry {
/**
* Set the rotation speed in radians/sec for particles
* spawned after the invocation of this method.
*
*
* @param rotateSpeed the rotation speed in radians/sec for particles
* spawned after the invocation of this method.
*/
@ -442,12 +504,12 @@ public class ParticleEmitter extends Geometry {
/**
* Returns true if every particle spawned
* should have a random facing angle.
*
* should have a random facing angle.
*
* @return true if every particle spawned
* should have a random facing angle.
*
* @see ParticleEmitter#setRandomAngle(boolean)
* should have a random facing angle.
*
* @see ParticleEmitter#setRandomAngle(boolean)
*/
public boolean isRandomAngle() {
return randomAngle;
@ -455,8 +517,8 @@ public class ParticleEmitter extends Geometry {
/**
* Set to true if every particle spawned
* should have a random facing angle.
*
* should have a random facing angle.
*
* @param randomAngle if every particle spawned
* should have a random facing angle.
*/
@ -467,11 +529,11 @@ public class ParticleEmitter extends Geometry {
/**
* Returns true if every particle spawned should get a random
* image.
*
*
* @return True if every particle spawned should get a random
* image.
*
* @see ParticleEmitter#setSelectRandomImage(boolean)
*
* @see ParticleEmitter#setSelectRandomImage(boolean)
*/
public boolean isSelectRandomImage() {
return selectRandomImage;
@ -481,7 +543,7 @@ public class ParticleEmitter extends Geometry {
* Set to true if every particle spawned
* should get a random image from a pool of images constructed from
* the texture, with X by Y possible images.
*
*
* <p>By default, X and Y are equal
* to 1, thus allowing only 1 possible image to be selected, but if the
* particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) }
@ -489,7 +551,7 @@ public class ParticleEmitter extends Geometry {
* can be selected. Setting to false will cause each particle to have an animation
* of images displayed, starting at image 1, and going until image X*Y when
* the particle reaches its end of life.
*
*
* @param selectRandomImage True if every particle spawned should get a random
* image.
*/
@ -499,10 +561,10 @@ public class ParticleEmitter extends Geometry {
/**
* Check if particles spawned should face their velocity.
*
*
* @return True if particles spawned should face their velocity.
*
* @see ParticleEmitter#setFacingVelocity(boolean)
*
* @see ParticleEmitter#setFacingVelocity(boolean)
*/
public boolean isFacingVelocity() {
return facingVelocity;
@ -511,11 +573,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set to true if particles spawned should face
* their velocity (or direction to which they are moving towards).
*
*
* <p>This is typically used for e.g spark effects.
*
*
* @param followVelocity True if particles spawned should face their velocity.
*
*
*/
public void setFacingVelocity(boolean followVelocity) {
this.facingVelocity = followVelocity;
@ -523,10 +585,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the end color of the particles spawned.
*
*
* @return the end color of the particles spawned.
*
* @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
*
* @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
*/
public ColorRGBA getEndColor() {
return endColor;
@ -534,12 +596,12 @@ public class ParticleEmitter extends Geometry {
/**
* Set the end color of the particles spawned.
*
*
* <p>The
* particle color at any time is determined by blending the start color
* and end color based on the particle's current time of life relative
* to its end of life.
*
*
* @param endColor the end color of the particles spawned.
*/
public void setEndColor(ColorRGBA endColor) {
@ -548,10 +610,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the end size of the particles spawned.
*
*
* @return the end size of the particles spawned.
*
* @see ParticleEmitter#setEndSize(float)
*
* @see ParticleEmitter#setEndSize(float)
*/
public float getEndSize() {
return endSize;
@ -559,12 +621,12 @@ public class ParticleEmitter extends Geometry {
/**
* Set the end size of the particles spawned.
*
*
* <p>The
* particle size at any time is determined by blending the start size
* and end size based on the particle's current time of life relative
* to its end of life.
*
*
* @param endSize the end size of the particles spawned.
*/
public void setEndSize(float endSize) {
@ -573,10 +635,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the gravity vector.
*
*
* @return the gravity vector.
*
* @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
*
* @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
*/
public Vector3f getGravity() {
return gravity;
@ -584,7 +646,7 @@ public class ParticleEmitter extends Geometry {
/**
* This method sets the gravity vector.
*
*
* @param gravity the gravity vector
*/
public void setGravity(Vector3f gravity) {
@ -593,7 +655,7 @@ public class ParticleEmitter extends Geometry {
/**
* Sets the gravity vector.
*
*
* @param x the x component of the gravity vector
* @param y the y component of the gravity vector
* @param z the z component of the gravity vector
@ -606,10 +668,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the high value of life.
*
*
* @return the high value of life.
*
* @see ParticleEmitter#setHighLife(float)
*
* @see ParticleEmitter#setHighLife(float)
*/
public float getHighLife() {
return highLife;
@ -617,10 +679,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the high value of life.
*
*
* <p>The particle's lifetime/expiration
* is determined by randomly selecting a time between low life and high life.
*
*
* @param highLife the high value of life.
*/
public void setHighLife(float highLife) {
@ -629,10 +691,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the number of images along the X axis (width).
*
*
* @return the number of images along the X axis (width).
*
* @see ParticleEmitter#setImagesX(int)
*
* @see ParticleEmitter#setImagesX(int)
*/
public int getImagesX() {
return imagesX;
@ -640,11 +702,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set the number of images along the X axis (width).
*
*
* <p>To determine
* how multiple particle images are selected and used, see the
* {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
*
*
* @param imagesX the number of images along the X axis (width).
*/
public void setImagesX(int imagesX) {
@ -654,10 +716,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the number of images along the Y axis (height).
*
*
* @return the number of images along the Y axis (height).
*
* @see ParticleEmitter#setImagesY(int)
*
* @see ParticleEmitter#setImagesY(int)
*/
public int getImagesY() {
return imagesY;
@ -665,10 +727,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the number of images along the Y axis (height).
*
*
* <p>To determine how multiple particle images are selected and used, see the
* {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
*
*
* @param imagesY the number of images along the Y axis (height).
*/
public void setImagesY(int imagesY) {
@ -678,10 +740,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the low value of life.
*
*
* @return the low value of life.
*
* @see ParticleEmitter#setLowLife(float)
*
* @see ParticleEmitter#setLowLife(float)
*/
public float getLowLife() {
return lowLife;
@ -689,10 +751,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the low value of life.
*
*
* <p>The particle's lifetime/expiration
* is determined by randomly selecting a time between low life and high life.
*
*
* @param lowLife the low value of life.
*/
public void setLowLife(float lowLife) {
@ -702,11 +764,11 @@ public class ParticleEmitter extends Geometry {
/**
* Get the number of particles to spawn per
* second.
*
*
* @return the number of particles to spawn per
* second.
*
* @see ParticleEmitter#setParticlesPerSec(float)
*
* @see ParticleEmitter#setParticlesPerSec(float)
*/
public float getParticlesPerSec() {
return particlesPerSec;
@ -715,7 +777,7 @@ public class ParticleEmitter extends Geometry {
/**
* Set the number of particles to spawn per
* second.
*
*
* @param particlesPerSec the number of particles to spawn per
* second.
*/
@ -723,13 +785,13 @@ public class ParticleEmitter extends Geometry {
this.particlesPerSec = particlesPerSec;
timeDifference = 0;
}
/**
* Get the start color of the particles spawned.
*
*
* @return the start color of the particles spawned.
*
* @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
*
* @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
*/
public ColorRGBA getStartColor() {
return startColor;
@ -737,11 +799,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set the start color of the particles spawned.
*
*
* <p>The particle color at any time is determined by blending the start color
* and end color based on the particle's current time of life relative
* to its end of life.
*
*
* @param startColor the start color of the particles spawned
*/
public void setStartColor(ColorRGBA startColor) {
@ -750,10 +812,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the start color of the particles spawned.
*
*
* @return the start color of the particles spawned.
*
* @see ParticleEmitter#setStartSize(float)
*
* @see ParticleEmitter#setStartSize(float)
*/
public float getStartSize() {
return startSize;
@ -761,11 +823,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set the start size of the particles spawned.
*
*
* <p>The particle size at any time is determined by blending the start size
* and end size based on the particle's current time of life relative
* to its end of life.
*
*
* @param startSize the start size of the particles spawned.
*/
public void setStartSize(float startSize) {
@ -788,10 +850,10 @@ public class ParticleEmitter extends Geometry {
* gravity.
*
* @deprecated
* This method is deprecated.
* This method is deprecated.
* Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead.
*
* @see ParticleEmitter#setVelocityVariation(float)
* @see ParticleEmitter#setVelocityVariation(float)
* @see ParticleEmitter#setGravity(float)
*/
@Deprecated
@ -801,7 +863,7 @@ public class ParticleEmitter extends Geometry {
/**
* @deprecated
* This method is deprecated.
* This method is deprecated.
* Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead.
* @return the initial velocity variation factor
*/
@ -816,9 +878,9 @@ public class ParticleEmitter extends Geometry {
* from 0 to 1, where 0 means particles are to spawn with exactly
* the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
* and 1 means particles are to spawn with a completely random velocity.
*
*
* @deprecated
* This method is deprecated.
* This method is deprecated.
* Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead.
*/
@Deprecated
@ -906,7 +968,7 @@ public class ParticleEmitter extends Geometry {
vars.release();
}
/**
* Instantly kills all active particles, after this method is called, all
* particles will be dead and no longer visible.
@ -918,12 +980,12 @@ public class ParticleEmitter extends Geometry {
}
}
}
/**
* Kills the particle at the given index.
*
*
* @param index The index of the particle to kill
* @see #getParticles()
* @see #getParticles()
*/
public void killParticle(int index){
freeParticle(index);
@ -978,7 +1040,7 @@ public class ParticleEmitter extends Geometry {
p.imageIndex = (int) (b * imagesX * imagesY);
}
}
private void updateParticleState(float tpf) {
// Force world transform to update
this.getWorldTransform();
@ -1011,7 +1073,7 @@ public class ParticleEmitter extends Geometry {
firstUnUsed++;
}
}
// Spawns particles within the tpf timeslot with proper age
float interval = 1f / particlesPerSec;
float originalTpf = tpf;
@ -1048,10 +1110,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set to enable or disable the particle emitter
*
*
* <p>When a particle is
* disabled, it will be "frozen in time" and not update.
*
*
* @param enabled True to enable the particle emitter
*/
public void setEnabled(boolean enabled) {
@ -1060,10 +1122,10 @@ public class ParticleEmitter extends Geometry {
/**
* Check if a particle emitter is enabled for update.
*
*
* @return True if a particle emitter is enabled for update.
*
* @see ParticleEmitter#setEnabled(boolean)
*
* @see ParticleEmitter#setEnabled(boolean)
*/
public boolean isEnabled() {
return enabled;
@ -1071,7 +1133,7 @@ public class ParticleEmitter extends Geometry {
/**
* Callback from Control.update(), do not use.
* @param tpf
* @param tpf
*/
public void updateFromControl(float tpf) {
if (enabled) {
@ -1081,9 +1143,9 @@ public class ParticleEmitter extends Geometry {
/**
* Callback from Control.render(), do not use.
*
*
* @param rm
* @param vp
* @param vp
*/
private void renderFromControl(RenderManager rm, ViewPort vp) {
Camera cam = vp.getCamera();
@ -1220,7 +1282,7 @@ public class ParticleEmitter extends Geometry {
gravity.y = ic.readFloat("gravity", 0);
}
} else {
// since the parentEmitter is not loaded, it must be
// since the parentEmitter is not loaded, it must be
// loaded separately
control = getControl(ParticleEmitterControl.class);
control.parentEmitter = this;

@ -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;
/**
@ -49,7 +51,7 @@ import java.io.IOException;
*/
public class DefaultParticleInfluencer implements ParticleInfluencer {
//Version #1 : changed startVelocity to initialvelocity for consistency with accessors
//Version #1 : changed startVelocity to initialvelocity for consistency with accessors
//and also changed it in serialization
public static final int SAVABLE_VERSION = 1;
/** Temporary variable used to help with calculations. */
@ -94,7 +96,7 @@ public class DefaultParticleInfluencer implements ParticleInfluencer {
initialVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone());
}else{
initialVelocity = (Vector3f) ic.readSavable("initialVelocity", Vector3f.ZERO.clone());
}
}
velocityVariation = ic.readFloat("variation", 0.2f);
}
@ -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;
/**
@ -81,7 +82,7 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* the origin used for computing the radial velocity direction
* @param origin
* @param origin
*/
public void setOrigin(Vector3f origin) {
this.origin = origin;
@ -97,7 +98,7 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* the radial velocity
* @param radialVelocity
* @param radialVelocity
*/
public void setRadialVelocity(float radialVelocity) {
this.radialVelocity = radialVelocity;
@ -105,7 +106,7 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* nullify y component of particle velocity to make the effect expand only on x and z axis
* @return
* @return
*/
public boolean isHorizontal() {
return horizontal;
@ -113,12 +114,24 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* nullify y component of particle velocity to make the effect expand only on x and z axis
* @param horizontal
* @param horizontal
*/
public void setHorizontal(boolean horizontal) {
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);
@ -180,7 +203,7 @@ public class EmitterMeshVertexShape implements EmitterShape {
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
this.vertices = ic.readSavableArrayList("vertices", null);
List<List<Vector3f>> tmpNormals = ic.readSavableArrayList("normals", null);
if (tmpNormals != null){
this.normals = tmpNormals;

@ -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;
}
@ -115,10 +137,10 @@ public class BitmapText extends Node {
*
* @param text String to change text to
*/
public void setText(String text) {
public void setText(String text) {
text = text == null ? "" : text;
if (text == block.getText() || block.getText().equals(text)) {
if (text == block.getText() || block.getText().equals(text)) {
return;
}
@ -126,24 +148,24 @@ public class BitmapText extends Node {
The problem with the below block is that StringBlock carries
pretty much all of the text-related state of the BitmapText such
as size, text box, alignment, etc.
I'm not sure why this change was needed and the commit message was
not entirely helpful because it purports to fix a problem that I've
not entirely helpful because it purports to fix a problem that I've
never encountered.
If block.setText("") doesn't do the right thing then that's where
the fix should go because StringBlock carries too much information to
be blown away every time. -pspeed
Change was made:
http://code.google.com/p/jmonkeyengine/source/detail?spec=svn9389&r=9389
Diff:
http://code.google.com/p/jmonkeyengine/source/diff?path=/trunk/engine/src/core/com/jme3/font/BitmapText.java&format=side&r=9389&old_path=/trunk/engine/src/core/com/jme3/font/BitmapText.java&old=8843
// If the text is empty, reset
if (text.isEmpty()) {
detachAllChildren();
for (int page = 0; page < textPages.length; page++) {
textPages[page] = new BitmapTextPage(font, true, page);
attachChild(textPages[page]);
@ -153,7 +175,7 @@ public class BitmapText extends Node {
letters = new Letters(font, block, letters.getQuad().isRightToLeft());
}
*/
// Update the text content
block.setText(text);
letters.setText(text);
@ -185,7 +207,7 @@ public class BitmapText extends Node {
letters.invalidate(); // TODO: Don't have to align.
needRefresh = true;
}
/**
* Sets an overall alpha that will be applied to all
* letters. If the alpha passed is -1 then alpha reverts
@ -196,7 +218,7 @@ public class BitmapText extends Node {
public void setAlpha(float alpha) {
letters.setBaseAlpha(alpha);
needRefresh = true;
}
}
public float getAlpha() {
return letters.getBaseAlpha();
@ -414,17 +436,17 @@ public class BitmapText extends Node {
if( mp == null ) {
return null;
}
return (ColorRGBA)mp.getValue();
return (ColorRGBA)mp.getValue();
}
public void render(RenderManager rm, ColorRGBA color) {
for (BitmapTextPage page : textPages) {
Material mat = page.getMaterial();
mat.setTexture("ColorMap", page.getTexture());
//ColorRGBA original = getColor(mat, "Color");
//ColorRGBA original = getColor(mat, "Color");
//mat.setColor("Color", color);
mat.render(page, rm);
//if( original == null ) {
// mat.clearParam("Color");
//} else {

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

@ -53,7 +53,7 @@ class Letters {
private ColorRGBA baseColor = null;
private float baseAlpha = -1;
private String plainText;
Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
final String text = bound.getText();
this.block = bound;
@ -78,10 +78,10 @@ class Letters {
// Give the letter a default color if
// one has been provided.
l.setColor( baseColor );
}
}
}
}
LinkedList<Range> ranges = colorTags.getTags();
if (!ranges.isEmpty()) {
for (int i = 0; i < ranges.size()-1; i++) {
@ -92,7 +92,7 @@ class Letters {
Range end = ranges.getLast();
setColor(end.start, plainText.length(), end.color);
}
invalidate();
}
@ -103,17 +103,17 @@ class Letters {
LetterQuad getTail() {
return tail;
}
void update() {
LetterQuad l = head;
int lineCount = 1;
BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
while (!l.isTail()) {
if (l.isInvalid()) {
l.update(block);
if (l.isInvalid(block)) {
switch (block.getLineWrapMode()) {
case Character:
@ -144,7 +144,7 @@ class Letters {
}
}
break;
case NoWrap:
case NoWrap:
LetterQuad cursor = l.getPrevious();
while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
cursor = cursor.getPrevious();
@ -158,10 +158,10 @@ class Letters {
cursor = cursor.getNext();
}
break;
case Clip:
case Clip:
// Clip the character that falls out of bounds
l.clip(block);
// Clear the rest up to the next line feed.
for( LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext() ) {
q.setBitmapChar(null);
@ -178,12 +178,12 @@ class Letters {
}
l = l.getNext();
}
align();
block.setLineCount(lineCount);
rewind();
}
private void align() {
final Align alignment = block.getAlignment();
final VAlign valignment = block.getVerticalAlignment();
@ -233,7 +233,7 @@ class Letters {
l.invalidate();
l.update(block); // TODO: update from l
}
float getCharacterX0() {
return current.getX0();
}
@ -241,54 +241,54 @@ class Letters {
float getCharacterY0() {
return current.getY0();
}
float getCharacterX1() {
return current.getX1();
}
float getCharacterY1() {
return current.getY1();
}
float getCharacterAlignX() {
return current.getAlignX();
}
float getCharacterAlignY() {
return current.getAlignY();
}
float getCharacterWidth() {
return current.getWidth();
}
float getCharacterHeight() {
return current.getHeight();
}
public boolean nextCharacter() {
if (current.isTail())
return false;
current = current.getNext();
return true;
}
public int getCharacterSetPage() {
return current.getBitmapChar().getPage();
}
public LetterQuad getQuad() {
return current;
}
public void rewind() {
current = head;
}
public void invalidate() {
invalidate(head);
}
public void invalidate(LetterQuad cursor) {
totalWidth = -1;
totalHeight = -1;
@ -298,7 +298,7 @@ class Letters {
cursor = cursor.getNext();
}
}
float getScale() {
return block.getSize() / font.getCharSet().getRenderedSize();
}
@ -306,7 +306,7 @@ class Letters {
public boolean isPrintable() {
return current.getBitmapChar() != null;
}
float getTotalWidth() {
validateSize();
return totalWidth;
@ -316,7 +316,7 @@ class Letters {
validateSize();
return totalHeight;
}
void validateSize() {
if (totalWidth < 0) {
LetterQuad l = head;
@ -371,11 +371,11 @@ class Letters {
cursor = cursor.getNext();
}
}
float getBaseAlpha() {
return baseAlpha;
}
void setBaseAlpha( float alpha ) { this.baseAlpha = alpha;
colorTags.setBaseAlpha(alpha);
@ -409,7 +409,7 @@ class Letters {
setColor(end.start, plainText.length(), end.color);
}
}
invalidate();
}

@ -43,13 +43,15 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* A camera that follows a spatial and can turn around it by dragging the mouse
* @author nehon
*/
public class ChaseCamera implements ActionListener, AnalogListener, Control {
public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable {
protected Spatial target = null;
protected float minVerticalRotation = 0.00f;
@ -567,6 +569,7 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
* @param spatial
* @return
*/
@Override
public Control cloneForSpatial(Spatial spatial) {
ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager);
cc.setMaxDistance(getMaxDistance());
@ -574,6 +577,23 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
return cc;
}
@Override
public Object jmeClone() {
ChaseCamera cc = new ChaseCamera(cam, inputManager);
cc.target = target;
cc.setMaxDistance(getMaxDistance());
cc.setMinDistance(getMinDistance());
return cc;
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.target = cloner.clone(target);
computePosition();
prevPos = new Vector3f(target.getWorldTranslation());
cam.setLocation(pos);
}
/**
* Sets the spacial for the camera control, should only be used internally
* @param spatial

@ -33,6 +33,8 @@ package com.jme3.light;
import com.jme3.export.*;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.SortUtil;
import java.io.IOException;
import java.util.*;
@ -40,10 +42,10 @@ import java.util.*;
/**
* <code>LightList</code> is used internally by {@link Spatial}s to manage
* lights that are attached to them.
*
*
* @author Kirill Vainer
*/
public final class LightList implements Iterable<Light>, Savable, Cloneable {
public final class LightList implements Iterable<Light>, Savable, Cloneable, JmeCloneable {
private Light[] list, tlist;
private float[] distToOwner;
@ -74,7 +76,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
/**
* Creates a <code>LightList</code> for the given {@link Spatial}.
*
*
* @param owner The spatial owner
*/
public LightList(Spatial owner) {
@ -87,7 +89,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
/**
* Set the owner of the LightList. Only used for cloning.
* @param owner
* @param owner
*/
public void setOwner(Spatial owner){
this.owner = owner;
@ -118,7 +120,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
/**
* Remove the light at the given index.
*
*
* @param index
*/
public void remove(int index){
@ -139,7 +141,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
/**
* Removes the given light from the LightList.
*
*
* @param l the light to remove
*/
public void remove(Light l){
@ -187,12 +189,12 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
/**
* Sorts the elements in the list according to their Comparator.
* There are two reasons why lights should be resorted.
* First, if the lights have moved, that means their distance to
* the spatial changed.
* Second, if the spatial itself moved, it means the distance from it to
* There are two reasons why lights should be resorted.
* First, if the lights have moved, that means their distance to
* the spatial changed.
* Second, if the spatial itself moved, it means the distance from it to
* the individual lights might have changed.
*
*
*
* @param transformChanged Whether the spatial's transform has changed
*/
@ -252,7 +254,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
list[p] = parent.list[i];
distToOwner[p] = Float.NEGATIVE_INFINITY;
}
listSize = local.listSize + parent.listSize;
}else{
listSize = local.listSize;
@ -261,7 +263,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
/**
* Returns an iterator that can be used to iterate over this LightList.
*
*
* @return an iterator that can be used to iterate over this LightList.
*/
public Iterator<Light> iterator() {
@ -276,10 +278,10 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
public Light next() {
if (!hasNext())
throw new NoSuchElementException();
return list[index++];
}
public void remove() {
LightList.this.remove(--index);
}
@ -290,7 +292,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
public LightList clone(){
try{
LightList clone = (LightList) super.clone();
clone.owner = null;
clone.list = list.clone();
clone.distToOwner = distToOwner.clone();
@ -302,6 +304,24 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
}
}
@Override
public LightList jmeClone() {
try{
LightList clone = (LightList)super.clone();
clone.tlist = null; // list used for sorting only
return clone;
}catch (CloneNotSupportedException ex){
throw new AssertionError();
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.owner = cloner.clone(owner);
this.list = cloner.clone(list);
this.distToOwner = cloner.clone(distToOwner);
}
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
// oc.write(owner, "owner", null);
@ -319,7 +339,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
List<Light> lights = ic.readSavableArrayList("lights", null);
listSize = lights.size();
// NOTE: make sure the array has a length of at least 1
int arraySize = Math.max(DEFAULT_SIZE, listSize);
list = new Light[arraySize];
@ -328,7 +348,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
for (int i = 0; i < listSize; i++){
list[i] = lights.get(i);
}
Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
}

@ -244,16 +244,45 @@ When arrays can be inserted in J3M files
if (texKey.isFlipY()) {
ret += "Flip ";
}
if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) {
ret += "Repeat ";
//Wrap mode
ret += getWrapMode(texVal, Texture.WrapAxis.S);
ret += getWrapMode(texVal, Texture.WrapAxis.T);
ret += getWrapMode(texVal, Texture.WrapAxis.R);
//Min and Mag filter
Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps;
if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){
def = Texture.MinFilter.Trilinear;
}
if(texVal.getMinFilter() != def){
ret += "Min" + texVal.getMinFilter().name()+ " ";
}
if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){
ret += "Mag" + texVal.getMagFilter().name()+ " ";
}
return ret + texKey.getName();
return ret + "\"" + texKey.getName() + "\"";
default:
return null; // parameter type not supported in J3M
}
}
private String getWrapMode(Texture texVal, Texture.WrapAxis axis) {
WrapMode mode = WrapMode.EdgeClamp;
try{
mode = texVal.getWrap(axis);
}catch (IllegalArgumentException e){
//this axis doesn't exist on the texture
return "";
}
if(mode != WrapMode.EdgeClamp){
return"Wrap"+ mode.name() + "_" + axis.name() + " ";
}
return "";
}
@Override
public MatParam clone() {
try {

@ -311,6 +311,8 @@ public class RenderState implements Cloneable, Savable {
boolean applyPolyOffset = true;
boolean stencilTest = false;
boolean applyStencilTest = false;
float lineWidth = 1;
boolean applyLineWidth = false;
TestFunction depthFunc = TestFunction.LessOrEqual;
//by default depth func will be applied anyway if depth test is applied
boolean applyDepthFunc = false;
@ -350,6 +352,9 @@ public class RenderState implements Cloneable, Savable {
oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);
oc.write(lineWidth, "lineWidth", 1);
// Only "additional render state" has them set to false by default
oc.write(applyPointSprite, "applyPointSprite", true);
@ -364,8 +369,7 @@ public class RenderState implements Cloneable, Savable {
oc.write(applyPolyOffset, "applyPolyOffset", true);
oc.write(applyDepthFunc, "applyDepthFunc", true);
oc.write(applyAlphaFunc, "applyAlphaFunc", false);
oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);
oc.write(applyLineWidth, "applyLineWidth", true);
}
@ -394,6 +398,8 @@ public class RenderState implements Cloneable, Savable {
backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater);
lineWidth = ic.readFloat("lineWidth", 1);
applyPointSprite = ic.readBoolean("applyPointSprite", true);
applyWireFrame = ic.readBoolean("applyWireFrame", true);
@ -407,6 +413,8 @@ public class RenderState implements Cloneable, Savable {
applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false);
applyLineWidth = ic.readBoolean("applyLineWidth", true);
}
@ -528,6 +536,10 @@ public class RenderState implements Cloneable, Savable {
}
}
if(lineWidth != rs.lineWidth){
return false;
}
return true;
}
@ -803,8 +815,17 @@ public class RenderState implements Cloneable, Savable {
this.alphaFunc = alphaFunc;
cachedHashCode = -1;
}
/**
* Sets the mesh line width.
* This is to use in conjunction with {@link #setWireframe(boolean)} or with a mesh in {@link Mesh.Mode#Lines} mode.
* @param lineWidth the line width.
*/
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
this.applyLineWidth = true;
cachedHashCode = -1;
}
/**
* Check if stencil test is enabled.
@ -1118,8 +1139,16 @@ public class RenderState implements Cloneable, Savable {
public TestFunction getAlphaFunc() {
return alphaFunc;
}
/**
* returns the wireframe line width
*
* @return the line width
*/
public float getLineWidth() {
return lineWidth;
}
public boolean isApplyAlphaFallOff() {
return applyAlphaFallOff;
@ -1168,8 +1197,10 @@ public class RenderState implements Cloneable, Savable {
public boolean isApplyAlphaFunc() {
return applyAlphaFunc;
}
public boolean isApplyLineWidth() {
return applyLineWidth;
}
/**
*
@ -1200,6 +1231,7 @@ public class RenderState implements Cloneable, Savable {
hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
hash = 79 * hash + Float.floatToIntBits(this.lineWidth);
cachedHashCode = hash;
}
return cachedHashCode;
@ -1324,6 +1356,11 @@ public class RenderState implements Cloneable, Savable {
state.frontStencilFunction = frontStencilFunction;
state.backStencilFunction = backStencilFunction;
}
if (additionalState.applyLineWidth) {
state.lineWidth = additionalState.lineWidth;
} else {
state.lineWidth = lineWidth;
}
state.cachedHashCode = -1;
return state;
}
@ -1351,6 +1388,7 @@ public class RenderState implements Cloneable, Savable {
backStencilFunction = state.backStencilFunction;
depthFunc = state.depthFunc;
alphaFunc = state.alphaFunc;
lineWidth = state.lineWidth;
applyPointSprite = true;
applyWireFrame = true;
@ -1364,6 +1402,7 @@ public class RenderState implements Cloneable, Savable {
applyPolyOffset = true;
applyDepthFunc = true;
applyAlphaFunc = false;
applyLineWidth = true;
}
@Override
@ -1392,7 +1431,8 @@ public class RenderState implements Cloneable, Savable {
+ "\noffsetEnabled=" + offsetEnabled
+ "\napplyPolyOffset=" + applyPolyOffset
+ "\noffsetFactor=" + offsetFactor
+ "\noffsetUnits=" + offsetUnits
+ "\noffsetUnits=" + offsetUnits
+ "\nlineWidth=" + lineWidth
+ "\n]";
}
}

@ -90,7 +90,7 @@ public class Spline implements Savable {
type = splineType;
this.curveTension = curveTension;
this.cycle = cycle;
this.computeTotalLentgh();
this.computeTotalLength();
}
/**
@ -116,7 +116,7 @@ public class Spline implements Savable {
this.controlPoints.addAll(controlPoints);
this.curveTension = curveTension;
this.cycle = cycle;
this.computeTotalLentgh();
this.computeTotalLength();
}
/**
@ -144,7 +144,7 @@ public class Spline implements Savable {
this.weights[i] = controlPoint.w;
}
CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
this.computeTotalLentgh();
this.computeTotalLength();
}
private void initCatmullRomWayPoints(List<Vector3f> list) {
@ -186,7 +186,7 @@ public class Spline implements Savable {
controlPoints.add(controlPoints.get(0).clone());
}
if (controlPoints.size() > 1) {
this.computeTotalLentgh();
this.computeTotalLength();
}
}
@ -197,7 +197,7 @@ public class Spline implements Savable {
public void removeControlPoint(Vector3f controlPoint) {
controlPoints.remove(controlPoint);
if (controlPoints.size() > 1) {
this.computeTotalLentgh();
this.computeTotalLength();
}
}
@ -209,7 +209,7 @@ public class Spline implements Savable {
/**
* This method computes the total length of the curve.
*/
private void computeTotalLentgh() {
private void computeTotalLength() {
totalLength = 0;
float l = 0;
if (segmentsLength == null) {
@ -317,7 +317,7 @@ public class Spline implements Savable {
public void setCurveTension(float curveTension) {
this.curveTension = curveTension;
if(type==SplineType.CatmullRom && !getControlPoints().isEmpty()) {
this.computeTotalLentgh();
this.computeTotalLength();
}
}
@ -342,7 +342,7 @@ public class Spline implements Savable {
controlPoints.add(controlPoints.get(0));
}
this.cycle = cycle;
this.computeTotalLentgh();
this.computeTotalLength();
} else {
this.cycle = cycle;
}
@ -369,7 +369,7 @@ public class Spline implements Savable {
*/
public void setType(SplineType type) {
this.type = type;
this.computeTotalLentgh();
this.computeTotalLength();
}
/**
@ -435,9 +435,13 @@ public class Spline implements Savable {
OutputCapsule oc = ex.getCapsule(this);
oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
oc.write(type, "type", SplineType.CatmullRom);
float list[] = new float[segmentsLength.size()];
for (int i = 0; i < segmentsLength.size(); i++) {
list[i] = segmentsLength.get(i);
float list[] = null;
if (segmentsLength != null) {
list = new float[segmentsLength.size()];
for (int i = 0; i < segmentsLength.size(); i++) {
list[i] = segmentsLength.get(i);
}
}
oc.write(list, "segmentsLength", null);
@ -454,7 +458,7 @@ public class Spline implements Savable {
public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this);
controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("wayPoints", null);
controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("controlPoints", new ArrayList<Vector3f>()); /* Empty List as default, prevents null pointers */
float list[] = in.readFloatArray("segmentsLength", null);
if (list != null) {
segmentsLength = new ArrayList<Float>();

@ -401,8 +401,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
viewPort.getCamera().setViewPort(left, right, bottom, top);
viewPort.setOutputFrameBuffer(outputBuffer);
viewPort = null;
renderFrameBuffer.dispose();
if(renderFrameBuffer != null){
renderFrameBuffer.dispose();
}
if(depthTexture!=null){
depthTexture.getImage().dispose();
}

@ -101,7 +101,7 @@ public class RenderContext {
public float pointSize = 1;
/**
* @see Mesh#setLineWidth(float)
* @see RenderState#setLineWidth(float)
*/
public float lineWidth = 1;

@ -152,7 +152,7 @@ public final class GLRenderer implements Renderer {
int major = Integer.parseInt(m.group(1));
int minor = Integer.parseInt(m.group(2));
if (minor >= 10 && minor % 10 == 0) {
// some versions can look like "1.30" instead of "1.3".
// some versions can look like "1.30" instead of "1.3".
// make sure to correct for this
minor /= 10;
}
@ -524,7 +524,7 @@ public final class GLRenderer implements Renderer {
// Initialize default state..
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
if (caps.contains(Caps.SeamlessCubemap)) {
// Enable this globally. Should be OK.
gl.glEnable(GLExt.GL_TEXTURE_CUBE_MAP_SEAMLESS);
@ -644,7 +644,7 @@ public final class GLRenderer implements Renderer {
gl.glDepthFunc(convertTestFunction(state.getDepthFunc()));
context.depthFunc = state.getDepthFunc();
}
if (state.isDepthWrite() && !context.depthWriteEnabled) {
gl.glDepthMask(true);
context.depthWriteEnabled = true;
@ -797,6 +797,10 @@ public final class GLRenderer implements Renderer {
gl.glDisable(GL.GL_STENCIL_TEST);
}
}
if (context.lineWidth != state.getLineWidth()) {
gl.glLineWidth(state.getLineWidth());
context.lineWidth = state.getLineWidth();
}
}
private int convertStencilOperation(StencilOperation stencilOp) {
@ -1094,7 +1098,7 @@ public final class GLRenderer implements Renderer {
if (gles2) {
// request GLSL ES (1.00) when compiling under GLES2.
stringBuf.append("#version 100\n");
if (source.getType() == ShaderType.Fragment) {
// GLES2 requires precision qualifier.
stringBuf.append("precision mediump float;\n");
@ -1481,7 +1485,7 @@ public final class GLRenderer implements Renderer {
rb.getId());
}
}
private void bindFrameBuffer(FrameBuffer fb) {
if (fb == null) {
if (context.boundFBO != 0) {
@ -1530,6 +1534,7 @@ public final class GLRenderer implements Renderer {
updateFrameBufferAttachment(fb, depthBuf);
}
setReadDrawBuffers(fb);
checkFrameBufferError();
@ -1570,11 +1575,11 @@ public final class GLRenderer implements Renderer {
if (gl2 == null) {
return;
}
final int NONE = -2;
final int INITIAL = -1;
final int MRT_OFF = 100;
if (fb == null) {
// Set Read/Draw buffers to initial value.
if (context.boundDrawBuf != INITIAL) {
@ -1638,9 +1643,9 @@ public final class GLRenderer implements Renderer {
}
}
}
}
public void setFrameBuffer(FrameBuffer fb) {
if (fb == null && mainFbOverride != null) {
fb = mainFbOverride;
@ -1884,7 +1889,7 @@ public final class GLRenderer implements Renderer {
if (image != null) {
haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps();
}
LastTextureState curState = image.getLastTextureState();
if (curState.magFilter != tex.getMagFilter()) {
@ -1947,7 +1952,7 @@ public final class GLRenderer implements Renderer {
}
curState.shadowCompareMode = texCompareMode;
}
// If at this point we didn't bind the texture, bind it now
bindTextureOnly(target, image, unit);
}
@ -1955,7 +1960,7 @@ public final class GLRenderer implements Renderer {
/**
* Validates if a potentially NPOT texture is supported by the hardware.
* <p>
* Textures with power-of-2 dimensions are supported on all hardware, however
* Textures with power-of-2 dimensions are supported on all hardware, however
* non-power-of-2 textures may or may not be supported depending on which
* texturing features are used.
*
@ -2010,7 +2015,7 @@ public final class GLRenderer implements Renderer {
/**
* Ensures that the texture is bound to the given unit
* and that the unit is currently active (for modification).
*
*
* @param target The texture target, one of GL_TEXTURE_***
* @param img The image texture to bind
* @param unit At what unit to bind the texture.
@ -2028,11 +2033,11 @@ public final class GLRenderer implements Renderer {
statistics.onTextureUse(img, false);
}
}
/**
* Ensures that the texture is bound to the given unit,
* but does not care if the unit is active (for rendering).
*
*
* @param target The texture target, one of GL_TEXTURE_***
* @param img The image texture to bind
* @param unit At what unit to bind the texture.
@ -2050,7 +2055,7 @@ public final class GLRenderer implements Renderer {
statistics.onTextureUse(img, false);
}
}
/**
* Uploads the given image to the GL driver.
*
@ -2074,7 +2079,6 @@ public final class GLRenderer implements Renderer {
// bind texture
int target = convertTextureType(type, img.getMultiSamples(), -1);
bindTextureAndUnit(target, img, unit);
if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
@ -2089,7 +2093,7 @@ public final class GLRenderer implements Renderer {
// We'll generate mipmaps via glGenerateMipmapEXT (see below)
}
} else if (img.hasMipmaps()) {
// Image already has mipmaps, set the max level based on the
// Image already has mipmaps, set the max level based on the
// number of mipmaps we have.
gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
} else {
@ -2825,7 +2829,8 @@ public final class GLRenderer implements Renderer {
throw new RendererException("Mesh instancing is not supported by the video hardware");
}
if (context.lineWidth != mesh.getLineWidth()) {
//this is kept for backward compatibility.
if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth();
}

@ -39,6 +39,7 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.util.clone.Cloner;
import com.jme3.util.SafeArrayList;
import java.io.IOException;
import java.util.*;
@ -50,7 +51,7 @@ import java.util.logging.Logger;
* The AssetLinkNode does not store its children when exported to file.
* Instead, you can add a list of AssetKeys that will be loaded and attached
* when the AssetLinkNode is restored.
*
*
* @author normenhansen
*/
public class AssetLinkNode extends Node {
@ -70,6 +71,20 @@ public class AssetLinkNode extends Node {
assetLoaderKeys.add(key);
}
/**
* 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 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
this.assetLoaderKeys = cloner.clone(assetLoaderKeys);
this.assetChildren = new HashMap<ModelKey, Spatial>();
}
/**
* Add a "linked" child. These are loaded from the assetManager when the
* AssetLinkNode is loaded from a binary file.
@ -166,7 +181,7 @@ public class AssetLinkNode extends Node {
children.add(child);
assetChildren.put(modelKey, child);
} else {
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
new Object[]{ modelKey, key });
}
}

@ -48,6 +48,8 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
/**
* BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph.
@ -60,7 +62,7 @@ import com.jme3.util.TempVars;
* Sub geoms can be removed but it may be slower than the normal spatial removing
* Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
* To integrate them in the batch you have to call the batch() method again on the batchNode.
*
*
* TODO normal or tangents or both looks a bit weird
* TODO more automagic (batch when needed in the updateLogicalState)
* @author Nehon
@ -77,7 +79,7 @@ public class BatchNode extends GeometryGroupNode {
*/
protected Map<Geometry, Batch> batchesByGeom = new HashMap<Geometry, Batch>();
/**
* used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
* used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
*/
private float[] tmpFloat;
private float[] tmpFloatN;
@ -96,7 +98,7 @@ public class BatchNode extends GeometryGroupNode {
public BatchNode(String name) {
super(name);
}
@Override
public void onTransformChange(Geometry geom) {
updateSubBatch(geom);
@ -123,7 +125,7 @@ public class BatchNode extends GeometryGroupNode {
protected Matrix4f getTransformMatrix(Geometry g){
return g.cachedWorldMat;
}
protected void updateSubBatch(Geometry bg) {
Batch batch = batchesByGeom.get(bg);
if (batch != null) {
@ -134,13 +136,13 @@ public class BatchNode extends GeometryGroupNode {
FloatBuffer posBuf = (FloatBuffer) pvb.getData();
VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer normBuf = (FloatBuffer) nvb.getData();
VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer onormBuf = (FloatBuffer) onvb.getData();
Matrix4f transformMat = getTransformMatrix(bg);
if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
@ -184,12 +186,12 @@ public class BatchNode extends GeometryGroupNode {
}
batches.clear();
batchesByGeom.clear();
}
}
//only reset maxVertCount if there is something new to batch
if (matMap.size() > 0) {
maxVertCount = 0;
}
for (Map.Entry<Material, List<Geometry>> entry : matMap.entrySet()) {
Mesh m = new Mesh();
Material material = entry.getKey();
@ -255,7 +257,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* recursively visit the subgraph and unbatch geometries
* @param s
* @param s
*/
private void unbatchSubGraph(Spatial s) {
if (s instanceof Node) {
@ -269,8 +271,8 @@ public class BatchNode extends GeometryGroupNode {
}
}
}
private void gatherGeometries(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
if (n instanceof Geometry) {
@ -283,7 +285,7 @@ public class BatchNode extends GeometryGroupNode {
}
List<Geometry> list = map.get(g.getMaterial());
if (list == null) {
//trying to compare materials with the isEqual method
//trying to compare materials with the isEqual method
for (Map.Entry<Material, List<Geometry>> mat : map.entrySet()) {
if (g.getMaterial().contentEquals(mat.getKey())) {
list = mat.getValue();
@ -331,7 +333,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* Sets the material to the all the batches of this BatchNode
* use setMaterial(Material material,int batchIndex) to set a material to a specific batch
*
*
* @param material the material to use for this geometry
*/
@Override
@ -341,12 +343,12 @@ public class BatchNode extends GeometryGroupNode {
/**
* Returns the material that is used for the first batch of this BatchNode
*
*
* use getMaterial(Material material,int batchIndex) to get a material from a specific batch
*
*
* @return the material that is used for the first batch of this BatchNode
*
* @see #setMaterial(com.jme3.material.Material)
*
* @see #setMaterial(com.jme3.material.Material)
*/
public Material getMaterial() {
if (!batches.isEmpty()) {
@ -359,7 +361,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* Merges all geometries in the collection into
* the output mesh. Does not take into account materials.
*
*
* @param geometries
* @param outMesh
*/
@ -383,7 +385,7 @@ public class BatchNode extends GeometryGroupNode {
maxVertCount = geom.getVertexCount();
}
Mesh.Mode listMode;
float listLineWidth = 1f;
//float listLineWidth = 1f;
int components;
switch (geom.getMesh().getMode()) {
case Points:
@ -394,7 +396,7 @@ public class BatchNode extends GeometryGroupNode {
case LineStrip:
case Lines:
listMode = Mesh.Mode.Lines;
listLineWidth = geom.getMesh().getLineWidth();
//listLineWidth = geom.getMesh().getLineWidth();
components = 2;
break;
case TriangleFan:
@ -418,7 +420,7 @@ public class BatchNode extends GeometryGroupNode {
formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat();
normForBuf[vb.getBufferType().ordinal()] = vb.isNormalized();
}
maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights());
if (mode != null && mode != listMode) {
@ -426,19 +428,20 @@ public class BatchNode extends GeometryGroupNode {
+ " primitive types: " + mode + " != " + listMode);
}
mode = listMode;
if (mode == Mesh.Mode.Lines) {
if (lineWidth != 1f && listLineWidth != lineWidth) {
throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
+ lineWidth + " != " + listLineWidth);
}
lineWidth = listLineWidth;
}
//Not needed anymore as lineWidth is now in RenderState and will be taken into account when merging according to the material
// if (mode == Mesh.Mode.Lines) {
// if (lineWidth != 1f && listLineWidth != lineWidth) {
// throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
// + lineWidth + " != " + listLineWidth);
// }
// lineWidth = listLineWidth;
// }
compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
}
outMesh.setMaxNumWeights(maxWeights);
outMesh.setMode(mode);
outMesh.setLineWidth(lineWidth);
//outMesh.setLineWidth(lineWidth);
if (totalVerts >= 65536) {
// make sure we create an UnsignedInt buffer so we can fit all of the meshes
formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
@ -585,7 +588,7 @@ public class BatchNode extends GeometryGroupNode {
int offset = start * 3;
int tanOffset = start * 4;
bindBufPos.rewind();
bindBufNorm.rewind();
bindBufTangents.rewind();
@ -661,10 +664,10 @@ public class BatchNode extends GeometryGroupNode {
vars.release();
}
protected class Batch {
protected class Batch implements JmeCloneable {
/**
* update the batchesByGeom map for this batch with the given List of geometries
* @param list
* @param list
*/
void updateGeomList(List<Geometry> list) {
for (Geometry geom : list) {
@ -674,6 +677,25 @@ public class BatchNode extends GeometryGroupNode {
}
}
Geometry geometry;
public final Geometry getGeometry() {
return geometry;
}
@Override
public Batch jmeClone() {
try {
return (Batch)super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.geometry = cloner.clone(geometry);
}
}
protected void setNeedsFullRebatch(boolean needsFullRebatch) {
@ -699,7 +721,27 @@ public class BatchNode extends GeometryGroupNode {
}
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.batches = cloner.clone(batches);
this.tmpFloat = cloner.clone(tmpFloat);
this.tmpFloatN = cloner.clone(tmpFloatN);
this.tmpFloatT = cloner.clone(tmpFloatT);
HashMap<Geometry, Batch> newBatchesByGeom = new HashMap<Geometry, Batch>();
for( Map.Entry<Geometry, Batch> e : batchesByGeom.entrySet() ) {
newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
}
this.batchesByGeom = newBatchesByGeom;
}
@Override
public int collideWith(Collidable other, CollisionResults results) {
int total = 0;

@ -36,6 +36,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.renderer.Camera;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
@ -93,7 +94,20 @@ public class CameraNode extends Node {
// this.lookAt(position, upVector);
// camControl.getCamera().lookAt(position, upVector);
// }
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// A change in behavior... I think previously CameraNode was probably
// not really cloneable... or at least its camControl would be pointing
// to the wrong control. -pspeed
this.camControl = cloner.clone(camControl);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);

@ -43,6 +43,8 @@ import com.jme3.material.Material;
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;
@ -54,12 +56,12 @@ import java.util.logging.Logger;
* contains the geometric data for rendering objects. It manages all rendering
* information such as a {@link Material} object to define how the surface
* should be shaded and the {@link Mesh} data to contain the actual geometry.
*
*
* @author Kirill Vainer
*/
public class Geometry extends Spatial {
// Version #1: removed shared meshes.
// Version #1: removed shared meshes.
// models loaded with shared mesh will be automatically fixed.
public static final int SAVABLE_VERSION = 1;
private static final Logger logger = Logger.getLogger(Geometry.class.getName());
@ -71,19 +73,19 @@ public class Geometry extends Spatial {
*/
protected boolean ignoreTransform = false;
protected transient Matrix4f cachedWorldMat = new Matrix4f();
/**
* Specifies which {@link GeometryGroupNode} this <code>Geometry</code>
* is managed by.
*/
protected GeometryGroupNode groupNode;
/**
* The start index of this <code>Geometry's</code> inside
* The start index of this <code>Geometry's</code> inside
* the {@link GeometryGroupNode}.
*/
protected int startIndex = -1;
/**
* Serialization only. Do not use.
*/
@ -95,37 +97,37 @@ public class Geometry extends Spatial {
* Create a geometry node without any mesh data.
* Both the mesh and the material are null, the geometry
* cannot be rendered until those are set.
*
*
* @param name The name of this geometry
*/
public Geometry(String name) {
super(name);
// For backwards compatibility, only clear the "requires
// update" flag if we are not a subclass of Geometry.
// This prevents subclass from silently failing to receive
// updates when they upgrade.
setRequiresUpdates(Geometry.class != getClass());
setRequiresUpdates(Geometry.class != getClass());
}
/**
* Create a geometry node with mesh data.
* The material of the geometry is null, it cannot
* be rendered until it is set.
*
*
* @param name The name of this geometry
* @param mesh The mesh data for this geometry
*/
public Geometry(String name, Mesh mesh) {
this(name);
if (mesh == null) {
throw new IllegalArgumentException("mesh cannot be null");
}
this.mesh = mesh;
}
@Override
public boolean checkCulling(Camera cam) {
if (isGrouped()) {
@ -137,8 +139,8 @@ public class Geometry extends Spatial {
/**
* @return If ignoreTransform mode is set.
*
* @see Geometry#setIgnoreTransform(boolean)
*
* @see Geometry#setIgnoreTransform(boolean)
*/
public boolean isIgnoreTransform() {
return ignoreTransform;
@ -156,7 +158,7 @@ public class Geometry extends Spatial {
* Level 0 indicates that the default index buffer should be used,
* levels [1, LodLevels + 1] represent the levels set on the mesh
* with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
*
*
* @param lod The lod level to set
*/
@Override
@ -170,7 +172,7 @@ public class Geometry extends Spatial {
}
lodLevel = lod;
if (isGrouped()) {
groupNode.onMeshChange(this);
}
@ -178,7 +180,7 @@ public class Geometry extends Spatial {
/**
* Returns the LOD level set with {@link #setLodLevel(int) }.
*
*
* @return the LOD level set
*/
public int getLodLevel() {
@ -187,10 +189,10 @@ public class Geometry extends Spatial {
/**
* Returns this geometry's mesh vertex count.
*
*
* @return this geometry's mesh vertex count.
*
* @see Mesh#getVertexCount()
*
* @see Mesh#getVertexCount()
*/
public int getVertexCount() {
return mesh.getVertexCount();
@ -198,10 +200,10 @@ public class Geometry extends Spatial {
/**
* Returns this geometry's mesh triangle count.
*
*
* @return this geometry's mesh triangle count.
*
* @see Mesh#getTriangleCount()
*
* @see Mesh#getTriangleCount()
*/
public int getTriangleCount() {
return mesh.getTriangleCount();
@ -209,9 +211,9 @@ public class Geometry extends Spatial {
/**
* Sets the mesh to use for this geometry when rendering.
*
*
* @param mesh the mesh to use for this geometry
*
*
* @throws IllegalArgumentException If mesh is null
*/
public void setMesh(Mesh mesh) {
@ -221,7 +223,7 @@ public class Geometry extends Spatial {
this.mesh = mesh;
setBoundRefresh();
if (isGrouped()) {
groupNode.onMeshChange(this);
}
@ -229,10 +231,10 @@ public class Geometry extends Spatial {
/**
* Returns the mesh to use for this geometry
*
*
* @return the mesh to use for this geometry
*
* @see #setMesh(com.jme3.scene.Mesh)
*
* @see #setMesh(com.jme3.scene.Mesh)
*/
public Mesh getMesh() {
return mesh;
@ -240,13 +242,13 @@ public class Geometry extends Spatial {
/**
* Sets the material to use for this geometry.
*
*
* @param material the material to use for this geometry
*/
@Override
public void setMaterial(Material material) {
this.material = material;
if (isGrouped()) {
groupNode.onMaterialChange(this);
}
@ -254,10 +256,10 @@ public class Geometry extends Spatial {
/**
* Returns the material that is used for this geometry.
*
*
* @return the material that is used for this geometry
*
* @see #setMaterial(com.jme3.material.Material)
*
* @see #setMaterial(com.jme3.material.Material)
*/
public Material getMaterial() {
return material;
@ -310,18 +312,18 @@ public class Geometry extends Spatial {
computeWorldMatrix();
if (isGrouped()) {
groupNode.onTransformChange(this);
groupNode.onTransformChange(this);
}
// geometry requires lights to be sorted
worldLights.sort(true);
}
/**
* Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
*
*
* Should only be called by the parent {@link GeometryGroupNode}.
*
*
* @param node Which {@link GeometryGroupNode} to associate with.
* @param startIndex The starting index of this geometry in the group.
*/
@ -329,26 +331,26 @@ public class Geometry extends Spatial {
if (isGrouped()) {
unassociateFromGroupNode();
}
this.groupNode = node;
this.startIndex = startIndex;
}
/**
* Removes the {@link GeometryGroupNode} association from this
* Removes the {@link GeometryGroupNode} association from this
* <code>Geometry</code>.
*
*
* Should only be called by the parent {@link GeometryGroupNode}.
*/
public void unassociateFromGroupNode() {
if (groupNode != null) {
// Once the geometry is removed
// Once the geometry is removed
// from the parent, the group node needs to be updated.
groupNode.onGeometryUnassociated(this);
groupNode = null;
// change the default to -1 to make error detection easier
startIndex = -1;
startIndex = -1;
}
}
@ -360,7 +362,7 @@ public class Geometry extends Spatial {
@Override
protected void setParent(Node parent) {
super.setParent(parent);
// If the geometry is managed by group node we need to unassociate.
if (parent == null && isGrouped()) {
unassociateFromGroupNode();
@ -406,7 +408,7 @@ public class Geometry extends Spatial {
* {@link Geometry#getWorldTransform() world transform} of this geometry.
* In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
* before using this method.
*
*
* @return Matrix to transform from local space to world space
*/
public Matrix4f getWorldMatrix() {
@ -418,7 +420,7 @@ public class Geometry extends Spatial {
* This alters the bound used on the mesh as well via
* {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
* forces the world bounding volume to be recomputed.
*
*
* @param modelBound The model bound to set
*/
@Override
@ -465,15 +467,15 @@ public class Geometry extends Spatial {
}
/**
* Determine whether this <code>Geometry</code> is managed by a
* Determine whether this <code>Geometry</code> is managed by a
* {@link GeometryGroupNode} or not.
*
*
* @return True if managed by a {@link GeometryGroupNode}.
*/
public boolean isGrouped() {
return groupNode != null;
}
/**
* @deprecated Use {@link #isGrouped()} instead.
*/
@ -491,15 +493,22 @@ 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,
// but the cloned one is not attached to anything, hence not managed.
if (geomClone.isGrouped()) {
geomClone.groupNode = null;
geomClone.startIndex = -1;
}
geomClone.cachedWorldMat = cachedWorldMat.clone();
if (material != null) {
if (cloneMaterial) {
@ -534,11 +543,58 @@ 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;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
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);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);

@ -36,11 +36,12 @@ import com.jme3.export.JmeImporter;
import com.jme3.light.Light;
import com.jme3.scene.control.LightControl;
import com.jme3.scene.control.LightControl.ControlDirection;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
* <code>LightNode</code> is used to link together a {@link Light} object
* with a {@link Node} object.
* with a {@link Node} object.
*
* @author Tim8Dev
*/
@ -66,7 +67,7 @@ public class LightNode extends Node {
/**
* Enable or disable the <code>LightNode</code> functionality.
*
*
* @param enabled If false, the functionality of LightNode will
* be disabled.
*/
@ -93,7 +94,20 @@ public class LightNode extends Node {
public Light getLight() {
return lightControl.getLight();
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields( Cloner cloner, Object original ) {
super.cloneFields(cloner, original);
// A change in behavior... I think previously LightNode was probably
// not really cloneable... or at least its lightControl would be pointing
// to the wrong control. -pspeed
this.lightControl = cloner.clone(lightControl);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);

@ -37,12 +37,12 @@ import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.collision.bih.BIHTree;
import com.jme3.export.*;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.Matrix4f;
import com.jme3.math.Triangle;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Renderer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
@ -50,8 +50,9 @@ import com.jme3.scene.mesh.*;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.NativeObject;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.*;
import java.util.ArrayList;
@ -62,18 +63,18 @@ import java.util.ArrayList;
* All visible elements in a scene are represented by meshes.
* Meshes may contain three types of geometric primitives:
* <ul>
* <li>Points - Every vertex represents a single point in space,
* <li>Points - Every vertex represents a single point in space,
* the size of each point is specified via {@link Mesh#setPointSize(float) }.
* Points can also be used for {@link RenderState#setPointSprite(boolean) point
* sprite} mode.</li>
* <li>Lines - 2 vertices represent a line segment, with the width specified
* via {@link Mesh#setLineWidth(float) }.</li>
* via {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}.</li>
* <li>Triangles - 3 vertices represent a solid triangle primitive. </li>
* </ul>
*
*
* @author Kirill Vainer
*/
public class Mesh extends NativeObject implements Savable {
public class Mesh extends NativeObject implements Savable, Cloneable, JmeCloneable {
/**
* The mode of the Mesh specifies both the type of primitive represented
@ -81,49 +82,49 @@ public class Mesh extends NativeObject implements Savable {
*/
public enum Mode {
/**
* A primitive is a single point in space. The size of the points
* A primitive is a single point in space. The size of the points
* can be specified with {@link Mesh#setPointSize(float) }.
*/
Points(true),
/**
* A primitive is a line segment. Every two vertices specify
* a single line. {@link Mesh#setLineWidth(float) } can be used
* a single line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used
* to set the width of the lines.
*/
Lines(true),
/**
* A primitive is a line segment. The first two vertices specify
* a single line, while subsequent vertices are combined with the
* previous vertex to make a line. {@link Mesh#setLineWidth(float) } can
* a single line, while subsequent vertices are combined with the
* previous vertex to make a line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can
* be used to set the width of the lines.
*/
LineStrip(false),
/**
* Identical to {@link #LineStrip} except that at the end
* the last vertex is connected with the first to form a line.
* {@link Mesh#setLineWidth(float) } can be used
* {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used
* to set the width of the lines.
*/
LineLoop(false),
/**
* A primitive is a triangle. Each 3 vertices specify a single
* triangle.
*/
Triangles(true),
/**
* Similar to {@link #Triangles}, the first 3 vertices
* Similar to {@link #Triangles}, the first 3 vertices
* specify a triangle, while subsequent vertices are combined with
* the previous two to form a triangle.
* the previous two to form a triangle.
*/
TriangleStrip(false),
/**
* Similar to {@link #Triangles}, the first 3 vertices
* Similar to {@link #Triangles}, the first 3 vertices
* specify a triangle, each 2 subsequent vertices are combined
* with the very first vertex to make a triangle.
*/
@ -136,20 +137,19 @@ public class Mesh extends NativeObject implements Savable {
* for each patch (default is 3 for triangle tesselation)
*/
Patch(true);
private boolean listMode = false;
private Mode(boolean listMode){
this.listMode = listMode;
}
/**
* Returns true if the specified mode is a list mode (meaning
* ,it specifies the indices as a linear list and not some special
* ,it specifies the indices as a linear list and not some special
* format).
* Will return true for the types {@link #Points}, {@link #Lines} and
* {@link #Triangles}.
*
*
* @return true if the mode is a list type mode
*/
public boolean isListMode(){
@ -169,7 +169,7 @@ public class Mesh extends NativeObject implements Savable {
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;
@ -195,7 +195,7 @@ public class Mesh extends NativeObject implements Savable {
* Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
* buffers} are shared between this and the clone mesh, the rest
* of the data is cloned.
*
*
* @return A shallow clone of the mesh
*/
@Override
@ -209,10 +209,10 @@ public class Mesh extends NativeObject implements Savable {
}
/**
* Creates a deep clone of this mesh.
* Creates a deep clone of this mesh.
* The {@link VertexBuffer vertex buffers} and the data inside them
* is cloned.
*
*
* @return a deep clone of this mesh.
*/
public Mesh deepClone(){
@ -248,14 +248,14 @@ public class Mesh extends NativeObject implements Savable {
* of the {@link VertexBuffer vertex buffer} data, however the
* {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers
* are deeply cloned.
*
*
* @return A clone of the mesh for animation use.
*/
public Mesh cloneForAnim(){
Mesh clone = clone();
if (getBuffer(Type.BindPosePosition) != null){
VertexBuffer oldPos = getBuffer(Type.Position);
// NOTE: creates deep clone
VertexBuffer newPos = oldPos.clone();
clone.clearBuffer(Type.Position);
@ -266,7 +266,7 @@ public class Mesh extends NativeObject implements Savable {
VertexBuffer newNorm = oldNorm.clone();
clone.clearBuffer(Type.Normal);
clone.setBuffer(newNorm);
if (getBuffer(Type.BindPoseTangent) != null){
VertexBuffer oldTang = getBuffer(Type.Tangent);
VertexBuffer newTang = oldTang.clone();
@ -278,14 +278,42 @@ public class Mesh extends NativeObject implements Savable {
return clone;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Mesh jmeClone() {
try {
Mesh clone = (Mesh)super.clone();
return 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 ) {
// Probably could clone this now but it will get regenerated anyway.
this.collisionTree = null;
this.meshBound = cloner.clone(meshBound);
this.buffersList = cloner.clone(buffersList);
this.buffers = cloner.clone(buffers);
this.lodLevels = cloner.clone(lodLevels);
}
/**
* Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
* and {@link Type#BindPoseTangent}
* and {@link Type#BindPoseTangent}
* buffers for this mesh by duplicating them based on the position and normal
* buffers already set on the mesh.
* This method does nothing if the mesh has no bone weight or index
* buffers.
*
*
* @param forSoftwareAnim Should be true if the bind pose is to be generated.
*/
public void generateBindPose(boolean forSoftwareAnim){
@ -318,7 +346,7 @@ public class Mesh extends NativeObject implements Savable {
setBuffer(bindNorm);
norm.setUsage(Usage.Stream);
}
VertexBuffer tangents = getBuffer(Type.Tangent);
if (tangents != null) {
VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
@ -334,8 +362,8 @@ public class Mesh extends NativeObject implements Savable {
/**
* Prepares the mesh for software skinning by converting the bone index
* and weight buffers to heap buffers.
*
* and weight buffers to heap buffers.
*
* @param forSoftwareAnim Should be true to enable the conversion.
*/
public void prepareForAnim(boolean forSoftwareAnim){
@ -379,7 +407,7 @@ public class Mesh extends NativeObject implements Savable {
}
} else {
//if HWBoneIndex and HWBoneWeight are empty, we setup them as direct
//buffers with software anim buffers data
//buffers with software anim buffers data
VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex);
if (indicesHW.getData() == null) {
VertexBuffer indices = getBuffer(Type.BoneIndex);
@ -389,7 +417,7 @@ public class Mesh extends NativeObject implements Savable {
directIndex.put(originalIndex);
indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex);
}
VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight);
if (weightsHW.getData() == null) {
VertexBuffer weights = getBuffer(Type.BoneWeight);
@ -399,26 +427,26 @@ public class Mesh extends NativeObject implements Savable {
directWeight.put(originalWeight);
weightsHW.setupData(Usage.Static, weights.getNumComponents(), weights.getFormat(), directWeight);
}
// position, normal, and tanget buffers to be in "Static" mode
VertexBuffer positions = getBuffer(Type.Position);
VertexBuffer normals = getBuffer(Type.Normal);
VertexBuffer tangents = getBuffer(Type.Tangent);
VertexBuffer positionsBP = getBuffer(Type.BindPosePosition);
VertexBuffer normalsBP = getBuffer(Type.BindPoseNormal);
VertexBuffer tangentsBP = getBuffer(Type.BindPoseTangent);
positions.setUsage(Usage.Static);
positionsBP.copyElements(0, positions, 0, positionsBP.getNumElements());
positions.setUpdateNeeded();
if (normals != null) {
normals.setUsage(Usage.Static);
normalsBP.copyElements(0, normals, 0, normalsBP.getNumElements());
normals.setUpdateNeeded();
}
if (tangents != null) {
tangents.setUsage(Usage.Static);
tangentsBP.copyElements(0, tangents, 0, tangentsBP.getNumElements());
@ -429,7 +457,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Set the LOD (level of detail) index buffers on this mesh.
*
*
* @param lodLevels The LOD levels to set
*/
public void setLodLevels(VertexBuffer[] lodLevels){
@ -446,15 +474,15 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the lod level at the given index.
*
*
* @param lod The lod level index, this does not include
* the main index buffer.
* @return The LOD index buffer at the index
*
* @throws IndexOutOfBoundsException If the index is outside of the
*
* @throws IndexOutOfBoundsException If the index is outside of the
* range [0, {@link #getNumLodLevels()}].
*
* @see #setLodLevels(com.jme3.scene.VertexBuffer[])
*
* @see #setLodLevels(com.jme3.scene.VertexBuffer[])
*/
public VertexBuffer getLodLevel(int lod){
return lodLevels[lod];
@ -462,10 +490,10 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the mesh mode
*
*
* @return the mesh mode
*
* @see #setMode(com.jme3.scene.Mesh.Mode)
*
* @see #setMode(com.jme3.scene.Mesh.Mode)
*/
public Mode getMode() {
return mode;
@ -473,9 +501,9 @@ public class Mesh extends NativeObject implements Savable {
/**
* Change the Mesh's mode. By default the mode is {@link Mode#Triangles}.
*
*
* @param mode The new mode to set
*
*
* @see Mode
*/
public void setMode(Mode mode) {
@ -485,10 +513,10 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the maximum number of weights per vertex on this mesh.
*
*
* @return maximum number of weights per vertex
*
* @see #setMaxNumWeights(int)
*
* @see #setMaxNumWeights(int)
*/
public int getMaxNumWeights() {
return maxNumWeights;
@ -498,8 +526,8 @@ public class Mesh extends NativeObject implements Savable {
* Set the maximum number of weights per vertex on this mesh.
* Only relevant if this mesh has bone index/weight buffers.
* This value should be between 0 and 4.
*
* @param maxNumWeights
*
* @param maxNumWeights
*/
public void setMaxNumWeights(int maxNumWeights) {
this.maxNumWeights = maxNumWeights;
@ -507,23 +535,23 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the size of points for point meshes
*
*
* @return the size of points
*
* @see #setPointSize(float)
*
* @see #setPointSize(float)
*/
public float getPointSize() {
return pointSize;
}
/**
* Set the size of points for meshes of mode {@link Mode#Points}.
* Set the size of points for meshes of mode {@link Mode#Points}.
* The point size is specified as on-screen pixels, the default
* value is 1.0. The point size
* does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
* render state is enabled, in that case, the vertex shader must specify the
* render state is enabled, in that case, the vertex shader must specify the
* point size by writing to <code>gl_PointSize</code>.
*
*
* @param pointSize The size of points
*/
public void setPointSize(float pointSize) {
@ -532,26 +560,30 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the line width for line meshes.
*
*
* @return the line width
* @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#getLineWidth()}
*/
@Deprecated
public float getLineWidth() {
return lineWidth;
}
/**
* Specify the line width for meshes of the line modes, such
* as {@link Mode#Lines}. The line width is specified as on-screen pixels,
* as {@link Mode#Lines}. The line width is specified as on-screen pixels,
* the default value is 1.0.
*
*
* @param lineWidth The line width
* @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}
*/
@Deprecated
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
}
/**
* Indicates to the GPU that this mesh will not be modified (a hint).
* Indicates to the GPU that this mesh will not be modified (a hint).
* Sets the usage mode to {@link Usage#Static}
* for all {@link VertexBuffer vertex buffers} on this Mesh.
*/
@ -592,7 +624,7 @@ public class Mesh extends NativeObject implements Savable {
public void setInterleaved(){
ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
vbs.addAll(buffersList);
// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
// index buffer not included when interleaving
vbs.remove(getBuffer(Type.Index));
@ -611,7 +643,7 @@ public class Mesh extends NativeObject implements Savable {
VertexBuffer allData = new VertexBuffer(Type.InterleavedData);
ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount());
allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf);
// adding buffer directly so that no update counts is forced
buffers.put(Type.InterleavedData.ordinal(), allData);
buffersList.add(allData);
@ -662,7 +694,7 @@ public class Mesh extends NativeObject implements Savable {
for (VertexBuffer vb : vbs){
vb.setOffset(offset);
vb.setStride(stride);
vb.updateData(null);
//vb.setupData(vb.usage, vb.components, vb.format, null);
offset += vb.componentsLength;
@ -696,20 +728,20 @@ public class Mesh extends NativeObject implements Savable {
int max = 0;
for( VertexBuffer vb : buffersList ) {
if( vb.getBaseInstanceCount() > max ) {
max = vb.getBaseInstanceCount();
}
}
max = vb.getBaseInstanceCount();
}
}
return max;
}
/**
* Update the {@link #getVertexCount() vertex} and
* Update the {@link #getVertexCount() vertex} and
* {@link #getTriangleCount() triangle} counts for this mesh
* based on the current data. This method should be called
* after the {@link Buffer#capacity() capacities} of the mesh's
* {@link VertexBuffer vertex buffers} has been altered.
*
* @throws IllegalStateException If this mesh is in
*
* @throws IllegalStateException If this mesh is in
* {@link #setInterleaved() interleaved} format.
*/
public void updateCounts(){
@ -739,7 +771,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the triangle count for the given LOD level.
*
*
* @param lod The lod level to look up
* @return The triangle count for that LOD level
*/
@ -762,10 +794,10 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns how many triangles or elements are on this Mesh.
* This value is only updated when {@link #updateCounts() } is called.
* If the mesh mode is not a triangle mode, then this returns the
* If the mesh mode is not a triangle mode, then this returns the
* number of elements/primitives, e.g. how many lines or how many points,
* instead of how many triangles.
*
*
* @return how many triangles/elements are on this Mesh.
*/
public int getTriangleCount(){
@ -774,30 +806,30 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the number of vertices on this mesh.
* The value is computed based on the position buffer, which
* The value is computed based on the position buffer, which
* must be set on all meshes.
*
*
* @return Number of vertices on the mesh
*/
public int getVertexCount(){
return vertCount;
}
/**
* Returns the number of instances this mesh contains. The instance
* count is based on any VertexBuffers with instancing set.
*/
public int getInstanceCount() {
return instanceCount;
}
}
/**
* Gets the triangle vertex positions at the given triangle index
* Gets the triangle vertex positions at the given triangle index
* and stores them into the v1, v2, v3 arguments.
*
* @param index The index of the triangle.
*
* @param index The index of the triangle.
* Should be between 0 and {@link #getTriangleCount()}.
*
*
* @param v1 Vector to contain first vertex position
* @param v2 Vector to contain second vertex position
* @param v3 Vector to contain third vertex position
@ -822,15 +854,15 @@ public class Mesh extends NativeObject implements Savable {
+ " has incompatible format");
}
}
/**
* Gets the triangle vertex positions at the given triangle index
* Gets the triangle vertex positions at the given triangle index
* and stores them into the {@link Triangle} argument.
* Also sets the triangle index to the <code>index</code> argument.
*
* @param index The index of the triangle.
*
* @param index The index of the triangle.
* Should be between 0 and {@link #getTriangleCount()}.
*
*
* @param tri The triangle to store the positions in
*/
public void getTriangle(int index, Triangle tri){
@ -840,12 +872,12 @@ public class Mesh extends NativeObject implements Savable {
}
/**
* Gets the triangle vertex indices at the given triangle index
* Gets the triangle vertex indices at the given triangle index
* and stores them into the given int array.
*
* @param index The index of the triangle.
*
* @param index The index of the triangle.
* Should be between 0 and {@link #getTriangleCount()}.
*
*
* @param indices Indices of the triangle's vertices
*/
public void getTriangle(int index, int[] indices){
@ -860,9 +892,9 @@ public class Mesh extends NativeObject implements Savable {
/**
* Generates a collision tree for the mesh.
* Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
* com.jme3.math.Matrix4f,
* com.jme3.bounding.BoundingVolume,
* Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
* com.jme3.math.Matrix4f,
* com.jme3.bounding.BoundingVolume,
* com.jme3.collision.CollisionResults) }.
*/
public void createCollisionData(){
@ -885,7 +917,7 @@ public class Mesh extends NativeObject implements Savable {
* User code should only use collideWith() on scene
* graph elements such as {@link Spatial}s.
*/
public int collideWith(Collidable other,
public int collideWith(Collidable other,
Matrix4f worldMatrix,
BoundingVolume worldBound,
CollisionResults results){
@ -893,18 +925,18 @@ public class Mesh extends NativeObject implements Savable {
if (getVertexCount() == 0) {
return 0;
}
if (collisionTree == null){
createCollisionData();
}
return collisionTree.collideWith(other, worldMatrix, worldBound, results);
}
/**
* Sets the {@link VertexBuffer} on the mesh.
* This will update the vertex/triangle counts if needed.
*
*
* @param vb The buffer to set
* @throws IllegalArgumentException If the buffer type is already set
*/
@ -916,12 +948,12 @@ public class Mesh extends NativeObject implements Savable {
buffersList.add(vb);
updateCounts();
}
/**
* Unsets the {@link VertexBuffer} set on this mesh
* with the given type. Does nothing if the vertex buffer type is not set
* with the given type. Does nothing if the vertex buffer type is not set
* initially.
*
*
* @param type The buffer type to remove
*/
public void clearBuffer(VertexBuffer.Type type){
@ -931,17 +963,17 @@ public class Mesh extends NativeObject implements Savable {
updateCounts();
}
}
/**
* Creates a {@link VertexBuffer} for the mesh or modifies
* the existing one per the parameters given.
*
*
* @param type The type of the buffer
* @param components Number of components
* @param format Data format
* @param buf The buffer data
*
* @throws UnsupportedOperationException If the buffer already set is
*
* @throws UnsupportedOperationException If the buffer already set is
* incompatible with the parameters given.
*/
public void setBuffer(Type type, int components, Format format, Buffer buf){
@ -959,16 +991,16 @@ public class Mesh extends NativeObject implements Savable {
updateCounts();
}
}
/**
* Set a floating point {@link VertexBuffer} on the mesh.
*
* @param type The type of {@link VertexBuffer},
* Set a floating point {@link VertexBuffer} on the mesh.
*
* @param type The type of {@link VertexBuffer},
* e.g. {@link Type#Position}, {@link Type#Normal}, etc.
*
*
* @param components Number of components on the vertex buffer, should
* be between 1 and 4.
*
*
* @param buf The floating point data to contain
*/
public void setBuffer(Type type, int components, FloatBuffer buf) {
@ -1006,9 +1038,9 @@ public class Mesh extends NativeObject implements Savable {
/**
* Get the {@link VertexBuffer} stored on this mesh with the given
* type.
*
*
* @param type The type of VertexBuffer
* @return the VertexBuffer data, or null if not set
* @return the VertexBuffer data, or null if not set
*/
public VertexBuffer getBuffer(Type type){
return buffers.get(type.ordinal());
@ -1017,7 +1049,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Get the {@link VertexBuffer} data stored on this mesh in float
* format.
*
*
* @param type The type of VertexBuffer
* @return the VertexBuffer data, or null if not set
*/
@ -1028,11 +1060,11 @@ public class Mesh extends NativeObject implements Savable {
return (FloatBuffer) vb.getData();
}
/**
* Get the {@link VertexBuffer} data stored on this mesh in short
* format.
*
*
* @param type The type of VertexBuffer
* @return the VertexBuffer data, or null if not set
*/
@ -1047,40 +1079,40 @@ public class Mesh extends NativeObject implements Savable {
/**
* Acquires an index buffer that will read the vertices on the mesh
* as a list.
*
*
* @return A virtual or wrapped index buffer to read the data as a list
*/
public IndexBuffer getIndicesAsList(){
IndexBuffer ib = getIndexBuffer();
if (ib != null) {
if (mode.isListMode()) {
if (ib != null){
if (mode.isListMode()){
// already in list mode
return ib;
} else {
}else{
// not in list mode but it does have an index buffer
// wrap it so the data is converted to list format
return new WrappedIndexBuffer(this);
}
} else {
}else{
// return a virtual index buffer that will supply
// "fake" indices in list format
return new VirtualIndexBuffer(vertCount, mode);
}
}
/**
* Get the index buffer for this mesh.
* Get the index buffer for this mesh.
* Will return <code>null</code> if no index buffer is set.
*
*
* @return The index buffer of this mesh.
*
*
* @see Type#Index
*/
public IndexBuffer getIndexBuffer() {
VertexBuffer vb = getBuffer(Type.Index);
if (vb == null)
return null;
return IndexBuffer.wrapIndexBuffer(vb.getData());
}
@ -1090,7 +1122,7 @@ public class Mesh extends NativeObject implements Savable {
* to index into the attributes of the other mesh.
* Note that this will also change this mesh's index buffer so that
* the references to the vertex data match the new indices.
*
*
* @param other The mesh to extract the vertex data from
*/
public void extractVertexData(Mesh other) {
@ -1110,7 +1142,7 @@ public class Mesh extends NativeObject implements Savable {
int oldIndex = indexBuf.get(i);
if (!oldIndicesToNewIndices.containsKey(oldIndex)) {
// this vertex has not been added, so allocate a
// this vertex has not been added, so allocate a
// new index for it and add it to the map
oldIndicesToNewIndices.put(oldIndex, newIndex);
newIndicesToOldIndices.add(oldIndex);
@ -1127,8 +1159,8 @@ public class Mesh extends NativeObject implements Savable {
throw new AssertionError();
}
// Create the new index buffer.
// Do not overwrite the old one because we might be able to
// Create the new index buffer.
// Do not overwrite the old one because we might be able to
// convert from int index buffer to short index buffer
IndexBuffer newIndexBuf;
if (newNumVerts >= 65536) {
@ -1144,10 +1176,10 @@ public class Mesh extends NativeObject implements Savable {
newIndexBuf.put(i, newIndex);
}
VertexBuffer newIdxBuf = new VertexBuffer(Type.Index);
newIdxBuf.setupData(oldIdxBuf.getUsage(),
oldIdxBuf.getNumComponents(),
newIdxBuf.setupData(oldIdxBuf.getUsage(),
oldIdxBuf.getNumComponents(),
newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort,
newIndexBuf.getBuffer());
clearBuffer(Type.Index);
@ -1163,7 +1195,7 @@ public class Mesh extends NativeObject implements Savable {
VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType());
newVb.setNormalized(oldVb.isNormalized());
//check for data before copying, some buffers are just empty shells
//check for data before copying, some buffers are just empty shells
//for caching purpose (HW skinning buffers), and will be filled when
//needed
if(oldVb.getData()!=null){
@ -1181,32 +1213,32 @@ public class Mesh extends NativeObject implements Savable {
oldVb.copyElement(oldIndex, newVb, i);
}
}
// Set the buffer on the mesh
clearBuffer(newVb.getBufferType());
setBuffer(newVb);
}
// Copy max weights per vertex as well
setMaxNumWeights(other.getMaxNumWeights());
// The data has been copied over, update informations
updateCounts();
updateBound();
}
/**
* Scales the texture coordinate buffer on this mesh by the given
* scale factor.
* scale factor.
* <p>
* Note that values above 1 will cause the
* texture to tile, while values below 1 will cause the texture
* Note that values above 1 will cause the
* texture to tile, while values below 1 will cause the texture
* to stretch.
* </p>
*
*
* @param scaleFactor The scale factor to scale by. Every texture
* coordinate is multiplied by this vector to get the result.
*
*
* @throws IllegalStateException If there's no texture coordinate
* buffer on the mesh
* @throws UnsupportedOperationException If the texture coordinate
@ -1238,7 +1270,7 @@ public class Mesh extends NativeObject implements Savable {
}
/**
* Updates the bounding volume of this mesh.
* Updates the bounding volume of this mesh.
* The method does nothing if the mesh has no {@link Type#Position} buffer.
* It is expected that the position buffer is a float buffer with 3 components.
*/
@ -1252,7 +1284,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the {@link BoundingVolume} of this Mesh.
* By default the bounding volume is a {@link BoundingBox}.
*
*
* @return the bounding volume of this mesh
*/
public BoundingVolume getBound() {
@ -1262,7 +1294,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Sets the {@link BoundingVolume} for this Mesh.
* The bounding volume is recomputed by calling {@link #updateBound() }.
*
*
* @param modelBound The model bound to set
*/
public void setBound(BoundingVolume modelBound) {
@ -1273,38 +1305,38 @@ public class Mesh extends NativeObject implements Savable {
* Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh.
* The integer key for the map is the {@link Enum#ordinal() ordinal}
* of the vertex buffer's {@link Type}.
* Note that the returned map is a reference to the map used internally,
* Note that the returned map is a reference to the map used internally,
* modifying it will cause undefined results.
*
*
* @return map of vertex buffers on this mesh.
*/
public IntMap<VertexBuffer> getBuffers(){
return buffers;
}
/**
* Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh.
* Using a list instead an IntMap via the {@link #getBuffers() } method is
* better for iteration as there's no need to create an iterator instance.
* Note that the returned list is a reference to the list used internally,
* modifying it will cause undefined results.
*
*
* @return list of vertex buffers on this mesh.
*/
public SafeArrayList<VertexBuffer> getBufferList(){
return buffersList;
}
/**
* Determines if the mesh uses bone animation.
*
*
* A mesh uses bone animation if it has bone index / weight buffers
* such as {@link Type#BoneIndex} or {@link Type#HWBoneIndex}.
*
*
* @return true if the mesh uses bone animation, false otherwise
*/
public boolean isAnimated() {
return getBuffer(Type.BoneIndex) != null ||
return getBuffer(Type.BoneIndex) != null ||
getBuffer(Type.HWBoneIndex) != null;
}
@ -1399,7 +1431,7 @@ public class Mesh extends NativeObject implements Savable {
}
//creating hw animation buffers empty so that they are put in the cache
if (isAnimated()) {
if(isAnimated()){
VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex);
hwBoneIndex.setUsage(Usage.CpuOnly);
setBuffer(hwBoneIndex);

@ -40,6 +40,7 @@ import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -53,7 +54,7 @@ import java.util.logging.Logger;
* node maintains a collection of children and handles merging said children
* into a single bound to allow for very fast culling of multiple nodes. Node
* allows for any number of children to be attached.
*
*
* @author Mark Powell
* @author Gregg Patton
* @author Joshua Slack
@ -62,26 +63,26 @@ public class Node extends Spatial {
private static final Logger logger = Logger.getLogger(Node.class.getName());
/**
/**
* This node's children.
*/
protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
/**
* If this node is a root, this list will contain the current
* set of children (and children of children) that require
* set of children (and children of children) that require
* updateLogicalState() to be called as indicated by their
* 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.
* A flag is used instead of nulling the updateList to avoid reallocating
* a whole list every time the scene graph changes.
*/
private boolean updateListValid = false;
*/
private boolean updateListValid = false;
/**
* Serialization only. Do not use.
@ -93,29 +94,29 @@ public class Node extends Spatial {
/**
* Constructor instantiates a new <code>Node</code> with a default empty
* list for containing children.
*
*
* @param name the name of the scene element. This is required for
* identification and comparison purposes.
*/
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
// updates when they upgrade.
setRequiresUpdates(Node.class != getClass());
setRequiresUpdates(Node.class != getClass());
}
/**
*
*
* <code>getQuantity</code> returns the number of children this node
* maintains.
*
*
* @return the number of children this node maintains.
*/
public int getQuantity() {
return children.size();
return children.size();
}
@Override
@ -155,7 +156,7 @@ public class Node extends Spatial {
@Override
protected void updateWorldBound(){
super.updateWorldBound();
// for a node, the world bound is a combination of all it's children
// bounds
BoundingVolume resultBound = null;
@ -179,7 +180,7 @@ public class Node extends Spatial {
protected void setParent(Node parent) {
if( this.parent == null && parent != null ) {
// We were a root before and now we aren't... make sure if
// we had an updateList then we clear it completely to
// we had an updateList then we clear it completely to
// avoid holding the dead array.
updateList = null;
updateListValid = false;
@ -216,15 +217,15 @@ public class Node extends Spatial {
return updateList;
}
if( updateList == null ) {
updateList = new SafeArrayList<Spatial>(Spatial.class);
updateList = new SafeArrayList<Spatial>(Spatial.class);
} else {
updateList.clear();
}
// Build the list
addUpdateChildren(updateList);
updateListValid = true;
return updateList;
updateListValid = true;
return updateList;
}
@Override
@ -250,7 +251,7 @@ public class Node extends Spatial {
// This branch has no geometric state that requires updates.
return;
}
if ((refreshFlags & RF_LIGHTLIST) != 0){
updateWorldLightList();
}
@ -266,7 +267,7 @@ public class Node extends Spatial {
}
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
@ -276,7 +277,7 @@ public class Node extends Spatial {
for (Spatial child : children.getArray()) {
child.updateGeometricState();
}
}
}
if ((refreshFlags & RF_BOUND) != 0){
updateWorldBound();
@ -288,7 +289,7 @@ public class Node extends Spatial {
/**
* <code>getTriangleCount</code> returns the number of triangles contained
* in all sub-branches of this node that contain geometry.
*
*
* @return the triangle count of this branch.
*/
@Override
@ -302,11 +303,11 @@ 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.
*
*
* @return the vertex count of this branch.
*/
@Override
@ -327,7 +328,7 @@ public class Node extends Spatial {
* returned.
* <br>
* If the child already had a parent it is detached from that former parent.
*
*
* @param child
* the child to attach to this node.
* @return the number of children maintained by this node.
@ -336,15 +337,15 @@ 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
* becomes the child's parent. The current number of children maintained is
* returned.
* <br>
* If the child already had a parent it is detached from that former parent.
*
*
* @param child
* the child to attach to this node.
* @return the number of children maintained by this node.
@ -360,28 +361,27 @@ 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();
}
/**
* <code>detachChild</code> removes a given child from the node's list.
* This child will no longer be maintained.
*
*
* @param child
* the child to remove.
* @return the index the child was at. -1 if the child was not in the list.
@ -396,16 +396,16 @@ public class Node extends Spatial {
detachChildAt(index);
}
return index;
}
return -1;
}
return -1;
}
/**
* <code>detachChild</code> removes a given child from the node's list.
* This child will no longe be maintained. Only the first child with a
* matching name is removed.
*
*
* @param childName
* the child to remove.
* @return the index the child was at. -1 if the child was not in the list.
@ -425,10 +425,10 @@ public class Node extends Spatial {
}
/**
*
*
* <code>detachChildAt</code> removes a child at a given index. That child
* is returned for saving purposes.
*
*
* @param index
* the index of the child to be removed.
* @return the child at the supplied index.
@ -457,7 +457,7 @@ public class Node extends Spatial {
}
/**
*
*
* <code>detachAllChildren</code> removes all children attached to this
* node.
*/
@ -476,7 +476,7 @@ public class Node extends Spatial {
* in this node's list of children.
* @param sp
* The spatial to look up
* @return
* @return
* The index of the spatial in the node's children, or -1
* if the spatial is not attached to this node
*/
@ -486,7 +486,7 @@ public class Node extends Spatial {
/**
* More efficient than e.g detaching and attaching as no updates are needed.
*
*
* @param index1 The index of the first child to swap
* @param index2 The index of the second child to swap
*/
@ -499,9 +499,9 @@ public class Node extends Spatial {
}
/**
*
*
* <code>getChild</code> returns a child at a given index.
*
*
* @param i
* the index to retrieve the child from.
* @return the child at a specified index.
@ -515,13 +515,13 @@ public class Node extends Spatial {
* given name (case sensitive.) This method does a depth first recursive
* search of all descendants of this node, it will return the first spatial
* found with a matching name.
*
*
* @param name
* the name of the child to retrieve. If null, we'll return null.
* @return the child if found, or null.
*/
public Spatial getChild(String name) {
if (name == null)
if (name == null)
return null;
for (Spatial child : children.getArray()) {
@ -536,11 +536,11 @@ public class Node extends Spatial {
}
return null;
}
/**
* determines if the provided Spatial is contained in the children list of
* this node.
*
*
* @param spat
* the child object to look for.
* @return true if the object is contained, false otherwise.
@ -584,39 +584,39 @@ 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.
// 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 ;))
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();
@ -660,7 +660,7 @@ public class Node extends Spatial {
* @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
*
* @see java.util.regex.Pattern
* @see Spatial#matches(java.lang.Class, java.lang.String)
* @see Spatial#matches(java.lang.Class, java.lang.String)
*/
@SuppressWarnings("unchecked")
public <T extends Spatial>List<T> descendantMatches(
@ -680,7 +680,7 @@ public class Node extends Spatial {
/**
* Convenience wrapper.
*
* @see #descendantMatches(java.lang.Class, java.lang.String)
* @see #descendantMatches(java.lang.Class, java.lang.String)
*/
public <T extends Spatial>List<T> descendantMatches(
Class<T> spatialSubclass) {
@ -690,7 +690,7 @@ public class Node extends Spatial {
/**
* Convenience wrapper.
*
* @see #descendantMatches(java.lang.Class, java.lang.String)
* @see #descendantMatches(java.lang.Class, java.lang.String)
*/
public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
return descendantMatches(null, nameRegex);
@ -709,12 +709,22 @@ 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){
@ -725,6 +735,21 @@ public class Node extends Spatial {
return nodeClone;
}
/**
* 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.children = cloner.clone(children);
// Only the outer cloning thing knows whether this should be nulled
// or not... after all, we might be cloning a root node in which case
// cloning this list is fine.
this.updateList = cloner.clone(updateList);
}
@Override
public void write(JmeExporter e) throws IOException {
super.write(e);
@ -736,8 +761,8 @@ 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,
children = new SafeArrayList( Spatial.class,
e.getCapsule(this).readSavableArrayList("children", null) );
// go through children and set parent to this node
@ -746,7 +771,7 @@ public class Node extends Spatial {
child.parent = this;
}
}
super.read(e);
}
@ -767,7 +792,7 @@ public class Node extends Spatial {
}
}
}
@Override
public void depthFirstTraversal(SceneGraphVisitor visitor) {
for (Spatial child : children.getArray()) {
@ -775,7 +800,7 @@ public class Node extends Spatial {
}
visitor.visit(this);
}
@Override
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
queue.addAll(children);

@ -48,6 +48,9 @@ import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.control.Control;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.IdentityCloneFunction;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import java.io.IOException;
@ -64,17 +67,17 @@ import java.util.logging.Logger;
* @author Joshua Slack
* @version $Revision: 4075 $, $Data$
*/
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset {
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable {
private static final Logger logger = Logger.getLogger(Spatial.class.getName());
/**
* Specifies how frustum culling should be handled by
* Specifies how frustum culling should be handled by
* this spatial.
*/
public enum CullHint {
/**
/**
* Do whatever our parent does. If no parent, default to {@link #Dynamic}.
*/
Inherit,
@ -84,13 +87,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Camera planes whether or not this Spatial should be culled.
*/
Dynamic,
/**
/**
* Always cull this from the view, throwing away this object
* and any children from rendering commands.
*/
Always,
/**
* Never cull this from view, always draw it.
* Never cull this from view, always draw it.
* Note that we will still get culled if our parent is culled.
*/
Never;
@ -101,15 +104,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
public enum BatchHint {
/**
/**
* Do whatever our parent does. If no parent, default to {@link #Always}.
*/
Inherit,
/**
/**
* This spatial will always be batched when attached to a BatchNode.
*/
Always,
/**
/**
* This spatial will never be batched when attached to a BatchNode.
*/
Never;
@ -119,13 +122,13 @@ 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_LIGHTLIST = 0x04, // changes in light lists
RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update
RF_MATPARAM_OVERRIDE = 0x10;
protected CullHint cullHint = CullHint.Inherit;
protected BatchHint batchHint = BatchHint.Inherit;
/**
/**
* Spatial's bounding volume relative to the world.
*/
protected BoundingVolume worldBound;
@ -138,7 +141,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected ArrayList<MatParamOverride> localOverrides;
protected ArrayList<MatParamOverride> worldOverrides;
/**
/**
* This spatial's name.
*/
protected String name;
@ -153,11 +156,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected HashMap<String, Savable> userData = null;
/**
* Used for smart asset caching
*
* @see AssetKey#useSmartCache()
*
* @see AssetKey#useSmartCache()
*/
protected AssetKey key;
/**
/**
* Spatial's parent, or null if it has none.
*/
protected transient Node parent;
@ -180,7 +183,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* Serialization only. Do not use.
* Not really. This class is never instantiated directly but the
* subclasses like to use the no-arg constructor for their own
* subclasses like to use the no-arg constructor for their own
* no-arg constructor... which is technically weaker than
* forward supplying defaults.
*/
@ -198,7 +201,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
protected Spatial(String name) {
this.name = name;
localTransform = new Transform();
worldTransform = new Transform();
@ -228,13 +231,13 @@ 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
* Subclasses can call this with true to denote that they require
* updateLogicalState() to be called even if they contain no controls.
* Setting this to false reverts to the default behavior of only
* updating if the spatial has controls. This is not meant to
* indicate dynamic state in any way and must be called while
* indicate dynamic state in any way and must be called while
* unattached or an IllegalStateException is thrown. It is designed
* to be called during object construction and then never changed, ie:
* it's meant to be subclass specific state and not runtime state.
@ -260,12 +263,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
// override it for more optimal behavior. Node and Geometry will override
// it to false if the class is Node.class or Geometry.class.
// This means that all subclasses will default to the old behavior
// unless they opt in.
// unless they opt in.
if( parent != null ) {
throw new IllegalStateException("setRequiresUpdates() cannot be called once attached.");
throw new IllegalStateException("setRequiresUpdates() cannot be called once attached.");
}
this.requiresUpdates = f;
}
}
/**
* Indicate that the transform of this spatial has changed and that
@ -278,17 +281,17 @@ 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) {
while (p != null) {
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;
}
@ -324,10 +327,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
p = p.parent;
}
}
/**
* (Internal use only) Forces a refresh of the given types of data.
*
*
* @param transforms Refresh world transform based on parents'
* @param bounds Refresh bounding volume data based on child nodes
* @param lights Refresh light list based on parents'
@ -410,9 +413,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* Returns the local {@link LightList}, which are the lights
* that were directly attached to this <code>Spatial</code> through the
* {@link #addLight(com.jme3.light.Light) } and
* {@link #addLight(com.jme3.light.Light) } and
* {@link #removeLight(com.jme3.light.Light) } methods.
*
*
* @return The local light list
*/
public LightList getLocalLightList() {
@ -423,7 +426,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Returns the world {@link LightList}, containing the lights
* combined from all this <code>Spatial's</code> parents up to and including
* this <code>Spatial</code>'s lights.
*
*
* @return The combined world light list
*/
public LightList getWorldLightList() {
@ -534,14 +537,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* <code>lookAt</code> is a convenience method for auto-setting the local
* rotation based on a position in world space and an up vector. It computes the rotation
* to transform the z-axis to point onto 'position' and the y-axis to 'up'.
* Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) }
* Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) }
* this method takes a world position to look at and not a relative direction.
*
* Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation.
* This was resulting in improper rotation when the spatial had rotated parent nodes.
* This method is intended to work in world space, so no matter what parent graph the
* This method is intended to work in world space, so no matter what parent graph the
* spatial has, it will look at the given position in world space.
*
*
* @param position
* where to look at in terms of world coordinates
* @param upVector
@ -554,10 +557,10 @@ 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);
getLocalRotation().lookAt(compVecA, upVector);
if ( getParent() != null ) {
Quaternion rot=vars.quat1;
rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
@ -750,7 +753,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* @param vp The ViewPort to which the Spatial is being rendered to.
*
* @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#getControl(java.lang.Class)
* @see Spatial#getControl(java.lang.Class)
*/
public void runControlRender(RenderManager rm, ViewPort vp) {
if (controls.isEmpty()) {
@ -766,26 +769,26 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Add a control to the list of controls.
* @param control The control to add.
*
* @see Spatial#removeControl(java.lang.Class)
* @see Spatial#removeControl(java.lang.Class)
*/
public void addControl(Control control) {
boolean before = requiresUpdates();
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.
if( parent != null && before != after ) {
parent.invalidateUpdateList();
parent.invalidateUpdateList();
}
}
/**
* Removes the first control that is an instance of the given class.
*
* @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#addControl(com.jme3.scene.control.Control)
*/
public void removeControl(Class<? extends Control> controlType) {
boolean before = requiresUpdates();
@ -797,23 +800,23 @@ 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();
parent.invalidateUpdateList();
}
}
/**
* Removes the given control from this spatial's controls.
*
*
* @param control The control to remove
* @return True if the control was successfully removed. False if the
* control is not assigned to this spatial.
*
* @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#addControl(com.jme3.scene.control.Control)
*/
public boolean removeControl(Control control) {
boolean before = requiresUpdates();
@ -823,14 +826,14 @@ 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();
parent.invalidateUpdateList();
}
return result;
}
@ -841,7 +844,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* @param controlType The superclass of the control to look for.
* @return The first instance in the list of the controlType class, or null.
*
* @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#addControl(com.jme3.scene.control.Control)
*/
public <T extends Control> T getControl(Class<T> controlType) {
for (Control c : controls.getArray()) {
@ -870,7 +873,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* @return The number of controls attached to this Spatial.
* @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#removeControl(java.lang.Class)
* @see Spatial#removeControl(java.lang.Class)
*/
public int getNumControls() {
return controls.size();
@ -895,7 +898,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Calling this when the Spatial is attached to a node
* will cause undefined results. User code should only call this
* method on Spatials having no parent.
*
*
* @see Spatial#getWorldLightList()
* @see Spatial#getWorldTransform()
* @see Spatial#getWorldBound()
@ -1150,9 +1153,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* <code>removeLight</code> removes the given light from the Spatial.
*
*
* @param light The light to remove.
* @see Spatial#addLight(com.jme3.light.Light)
* @see Spatial#addLight(com.jme3.light.Light)
*/
public void removeLight(Light light) {
localLights.remove(light);
@ -1344,12 +1347,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()
* @see Mesh#cloneForAnim()
*/
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 clone(boolean cloneMaterial) {
public Spatial oldClone(boolean cloneMaterial) {
try {
Spatial clone = (Spatial) super.clone();
if (worldBound != null) {
@ -1419,7 +1453,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* All controls will be cloned using the Control.cloneForSpatial method
* on the clone.
*
* @see Mesh#cloneForAnim()
* @see Mesh#cloneForAnim()
*/
@Override
public Spatial clone() {
@ -1433,7 +1467,73 @@ 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.
*/
@Override
public Spatial jmeClone() {
try {
Spatial clone = (Spatial)super.clone();
return 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 ) {
// Clone all of the fields that need fix-ups and/or potential
// sharing.
this.parent = cloner.clone(parent);
this.worldBound = cloner.clone(worldBound);
this.worldLights = cloner.clone(worldLights);
this.localLights = cloner.clone(localLights);
this.worldTransform = cloner.clone(worldTransform);
this.localTransform = cloner.clone(localTransform);
clone.worldOverrides = cloner.clone(worldOverrides);
clone.localOverrides = cloner.clone(localOverrides);
this.controls = cloner.clone(controls);
// Cloner doesn't handle maps on its own just yet.
// Note: this is more advanced cloning than the old clone() method
// did because it just shallow cloned the map. In this case, we want
// 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.
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));
}
}
}
}
public void setUserData(String key, Object data) {
if (userData == null) {
@ -1441,7 +1541,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
}
if(data == null){
userData.remove(key);
userData.remove(key);
}else if (data instanceof Savable) {
userData.put(key, (Savable) data);
} else {
@ -1543,7 +1643,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
//the AnimControl creates the SkeletonControl for old files and add it to the spatial.
//The SkeletonControl must be the last in the stack so we add the list of all other control before it.
//When backward compatibility won't be needed anymore this can be replaced by :
//When backward compatibility won't be needed anymore this can be replaced by :
//controls = ic.readSavableArrayList("controlsList", null));
controls.addAll(0, ic.readSavableArrayList("controlsList", null));
@ -1606,9 +1706,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* <code>setQueueBucket</code> determines at what phase of the
* rendering process this Spatial will rendered. See the
* {@link Bucket} enum for an explanation of the various
* {@link Bucket} enum for an explanation of the various
* render queue buckets.
*
*
* @param queueBucket
* The bucket to use for this Spatial.
*/
@ -1693,7 +1793,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*
* @return store if not null, otherwise, a new matrix containing the result.
*
* @see Spatial#getWorldTransform()
* @see Spatial#getWorldTransform()
*/
public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
if (store == null) {

@ -38,6 +38,8 @@ import com.jme3.export.OutputCapsule;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -45,7 +47,7 @@ import java.io.IOException;
*
* @author Kirill Vainer
*/
public abstract class AbstractControl implements Control {
public abstract class AbstractControl implements Control, JmeCloneable {
protected boolean enabled = true;
protected Spatial spatial;
@ -105,6 +107,20 @@ public abstract class AbstractControl implements Control {
}
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException( "Can't clone control for spatial", e );
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.spatial = cloner.clone(spatial);
}
public void update(float tpf) {
if (!enabled)
return;

@ -86,12 +86,13 @@ public class BillboardControl extends AbstractControl {
alignment = Alignment.Screen;
}
public Control cloneForSpatial(Spatial spatial) {
BillboardControl control = new BillboardControl();
control.alignment = this.alignment;
control.setSpatial(spatial);
return control;
}
// default implementation from AbstractControl is equivalent
//public Control cloneForSpatial(Spatial spatial) {
// BillboardControl control = new BillboardControl();
// control.alignment = this.alignment;
// control.setSpatial(spatial);
// return control;
//}
@Override
protected void controlUpdate(float tpf) {

@ -136,13 +136,14 @@ public class CameraControl extends AbstractControl {
// nothing to do
}
@Override
public Control cloneForSpatial(Spatial newSpatial) {
CameraControl control = new CameraControl(camera, controlDir);
control.setSpatial(newSpatial);
control.setEnabled(isEnabled());
return control;
}
// default implementation from AbstractControl is equivalent
//@Override
//public Control cloneForSpatial(Spatial newSpatial) {
// CameraControl control = new CameraControl(camera, controlDir);
// control.setSpatial(newSpatial);
// control.setEnabled(isEnabled());
// return control;
//}
private static final String CONTROL_DIR_NAME = "controlDir";
private static final String CAMERA_NAME = "camera";

@ -167,13 +167,14 @@ public class LightControl extends AbstractControl {
// nothing to do
}
@Override
public Control cloneForSpatial(Spatial newSpatial) {
LightControl control = new LightControl(light, controlDir);
control.setSpatial(newSpatial);
control.setEnabled(isEnabled());
return control;
}
// default implementation from AbstractControl is equivalent
//@Override
//public Control cloneForSpatial(Spatial newSpatial) {
// LightControl control = new LightControl(light, controlDir);
// control.setSpatial(newSpatial);
// control.setEnabled(isEnabled());
// return control;
//}
private static final String CONTROL_DIR_NAME = "controlDir";
private static final String LIGHT_NAME = "light";

@ -43,6 +43,8 @@ import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@ -56,7 +58,7 @@ import java.io.IOException;
* and will update the spatial's LOD if the camera has moved by a specified
* amount.
*/
public class LodControl extends AbstractControl implements Cloneable {
public class LodControl extends AbstractControl implements Cloneable, JmeCloneable {
private float trisPerPixel = 1f;
private float distTolerance = 1f;
@ -140,7 +142,16 @@ public class LodControl extends AbstractControl implements Cloneable {
clone.lastLevel = 0;
clone.numTris = numTris != null ? numTris.clone() : null;
return clone;
}
}
@Override
public Object jmeClone() {
LodControl clone = (LodControl)super.jmeClone();
clone.lastDistance = 0;
clone.lastLevel = 0;
clone.numTris = numTris != null ? numTris.clone() : null;
return clone;
}
@Override
protected void controlUpdate(float tpf) {

@ -35,6 +35,8 @@ import com.jme3.app.AppTask;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
@ -85,6 +87,7 @@ public class UpdateControl extends AbstractControl {
}
@Override
public Control cloneForSpatial(Spatial newSpatial) {
UpdateControl control = new UpdateControl();
control.setSpatial(newSpatial);
@ -93,4 +96,15 @@ public class UpdateControl extends AbstractControl {
return control;
}
@Override
public Object jmeClone() {
UpdateControl clone = (UpdateControl)super.jmeClone();
// This is kind of questionable since the tasks aren't cloned and have
// no reference to the new spatial or anything. They'll get run again
// but it's not clear to me why that would be desired. I'm doing it
// because the old cloneForSpatial() code does. FIXME? -pspeed
clone.taskQueue.addAll(taskQueue);
return clone;
}
}

@ -47,19 +47,20 @@ import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
public class InstancedGeometry extends Geometry {
private static final int INSTANCE_SIZE = 16;
private VertexBuffer[] globalInstanceData;
private VertexBuffer transformInstanceData;
private Geometry[] geometries = new Geometry[1];
private int firstUnusedIndex = 0;
/**
@ -71,12 +72,12 @@ public class InstancedGeometry extends Geometry {
setBatchHint(BatchHint.Never);
setMaxNumInstances(1);
}
/**
* Creates instanced geometry with the specified mode and name.
*
* @param name The name of the spatial.
*
*
* @param name The name of the spatial.
*
* @see Spatial#Spatial(java.lang.String)
*/
public InstancedGeometry(String name) {
@ -85,57 +86,57 @@ public class InstancedGeometry extends Geometry {
setBatchHint(BatchHint.Never);
setMaxNumInstances(1);
}
/**
* Global user specified per-instance data.
*
* Global user specified per-instance data.
*
* By default set to <code>null</code>, specify an array of VertexBuffers
* via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
*
* @return global user specified per-instance data.
* @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
*
* @return global user specified per-instance data.
* @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
*/
public VertexBuffer[] getGlobalUserInstanceData() {
return globalInstanceData;
}
/**
* Specify global user per-instance data.
*
*
* By default set to <code>null</code>, specify an array of VertexBuffers
* that contain per-instance vertex attributes.
*
*
* @param globalInstanceData global user per-instance data.
*
* @throws IllegalArgumentException If one of the VertexBuffers is not
*
* @throws IllegalArgumentException If one of the VertexBuffers is not
* {@link VertexBuffer#setInstanced(boolean) instanced}.
*/
public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
this.globalInstanceData = globalInstanceData;
}
/**
* Specify camera specific user per-instance data.
*
*
* @param transformInstanceData The transforms for each instance.
*/
public void setTransformUserInstanceData(VertexBuffer transformInstanceData) {
this.transformInstanceData = transformInstanceData;
}
/**
* Return user per-instance transform data.
*
*
* @return The per-instance transform data.
*
* @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer)
* @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer)
*/
public VertexBuffer getTransformUserInstanceData() {
return transformInstanceData;
}
private void updateInstance(Matrix4f worldMatrix, float[] store,
int offset, Matrix3f tempMat3,
private void updateInstance(Matrix4f worldMatrix, float[] store,
int offset, Matrix3f tempMat3,
Quaternion tempQuat) {
worldMatrix.toRotationMatrix(tempMat3);
tempMat3.invertLocal();
@ -164,17 +165,17 @@ public class InstancedGeometry extends Geometry {
store[offset + 14] = worldMatrix.m23;
store[offset + 15] = tempQuat.getW();
}
/**
* Set the maximum amount of instances that can be rendered by this
* instanced geometry when mode is set to auto.
*
*
* This re-allocates internal structures and therefore should be called
* only when necessary.
*
* only when necessary.
*
* @param maxNumInstances The maximum number of instances that can be
* rendered.
*
*
* @throws IllegalStateException If mode is set to manual.
* @throws IllegalArgumentException If maxNumInstances is zero or negative
*/
@ -182,14 +183,14 @@ public class InstancedGeometry extends Geometry {
if (maxNumInstances < 1) {
throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
}
Geometry[] originalGeometries = geometries;
this.geometries = new Geometry[maxNumInstances];
if (originalGeometries != null) {
System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length);
}
// Resize instance data.
if (transformInstanceData != null) {
BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
@ -203,7 +204,7 @@ public class InstancedGeometry extends Geometry {
BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
}
}
public int getMaxNumInstances() {
return geometries.length;
}
@ -211,12 +212,12 @@ public class InstancedGeometry extends Geometry {
public int getActualNumInstances() {
return firstUnusedIndex;
}
private void swap(int idx1, int idx2) {
Geometry g = geometries[idx1];
geometries[idx1] = geometries[idx2];
geometries[idx2] = g;
if (geometries[idx1] != null) {
InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1);
}
@ -224,7 +225,7 @@ public class InstancedGeometry extends Geometry {
InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2);
}
}
private void sanitize(boolean insideEntriesNonNull) {
if (firstUnusedIndex >= geometries.length) {
throw new AssertionError();
@ -234,7 +235,7 @@ public class InstancedGeometry extends Geometry {
if (geometries[i] == null) {
if (insideEntriesNonNull) {
throw new AssertionError();
}
}
} else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) {
throw new AssertionError();
}
@ -245,55 +246,55 @@ public class InstancedGeometry extends Geometry {
}
}
}
public void updateInstances() {
FloatBuffer fb = (FloatBuffer) transformInstanceData.getData();
fb.limit(fb.capacity());
fb.position(0);
TempVars vars = TempVars.get();
{
float[] temp = vars.matrixWrite;
for (int i = 0; i < firstUnusedIndex; i++) {
Geometry geom = geometries[i];
if (geom == null) {
geom = geometries[firstUnusedIndex - 1];
if (geom == null) {
throw new AssertionError();
}
swap(i, firstUnusedIndex - 1);
while (geometries[firstUnusedIndex -1] == null) {
firstUnusedIndex--;
}
}
Matrix4f worldMatrix = geom.getWorldMatrix();
updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1);
fb.put(temp);
}
}
vars.release();
fb.flip();
if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) {
throw new AssertionError();
}
transformInstanceData.updateData(fb);
}
public void deleteInstance(Geometry geom) {
int idx = InstancedNode.getGeometryStartIndex2(geom);
InstancedNode.setGeometryStartIndex2(geom, -1);
geometries[idx] = null;
if (idx == firstUnusedIndex - 1) {
// Deleting the last element.
// Move index back.
@ -309,12 +310,12 @@ public class InstancedGeometry extends Geometry {
// Deleting element in the middle
}
}
public void addInstance(Geometry geometry) {
if (geometry == null) {
throw new IllegalArgumentException("geometry cannot be null");
}
// Take an index from the end.
if (firstUnusedIndex + 1 >= geometries.length) {
// No more room.
@ -323,15 +324,15 @@ public class InstancedGeometry extends Geometry {
int freeIndex = firstUnusedIndex;
firstUnusedIndex++;
geometries[freeIndex] = geometry;
InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
}
public Geometry[] getGeometries() {
return geometries;
}
public VertexBuffer[] getAllInstanceData() {
ArrayList<VertexBuffer> allData = new ArrayList();
if (transformInstanceData != null) {
@ -343,6 +344,18 @@ public class InstancedGeometry extends Geometry {
return allData.toArray(new VertexBuffer[allData.size()]);
}
/**
* 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.globalInstanceData = cloner.clone(globalInstanceData);
this.transformInstanceData = cloner.clone(transformInstanceData);
this.geometries = cloner.clone(geometries);
}
@Override
public void write(JmeExporter exporter) throws IOException {
super.write(exporter);
@ -350,7 +363,7 @@ public class InstancedGeometry extends Geometry {
//capsule.write(currentNumInstances, "cur_num_instances", 1);
capsule.write(geometries, "geometries", null);
}
@Override
public void read(JmeImporter importer) throws IOException {
super.read(importer);

@ -44,20 +44,23 @@ import com.jme3.scene.control.Control;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.MatParam;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class InstancedNode extends GeometryGroupNode {
static int getGeometryStartIndex2(Geometry geom) {
return getGeometryStartIndex(geom);
}
static void setGeometryStartIndex2(Geometry geom, int startIndex) {
setGeometryStartIndex(geom, startIndex);
}
private static final class InstanceTypeKey implements Cloneable {
private static final class InstanceTypeKey implements Cloneable, JmeCloneable {
Mesh mesh;
Material material;
@ -68,7 +71,7 @@ public class InstancedNode extends GeometryGroupNode {
this.material = material;
this.lodLevel = lodLevel;
}
public InstanceTypeKey(){
}
@ -95,7 +98,7 @@ public class InstancedNode extends GeometryGroupNode {
}
return true;
}
@Override
public InstanceTypeKey clone() {
try {
@ -104,65 +107,94 @@ public class InstancedNode extends GeometryGroupNode {
throw new AssertionError();
}
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new AssertionError();
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.mesh = cloner.clone(mesh);
this.material = cloner.clone(material);
}
}
private static class InstancedNodeControl implements Control {
private static class InstancedNodeControl implements Control, JmeCloneable {
private InstancedNode node;
public InstancedNodeControl() {
}
public InstancedNodeControl(InstancedNode node) {
this.node = node;
}
@Override
public Control cloneForSpatial(Spatial spatial) {
return this;
return this;
// WARNING: Sets wrong control on spatial. Will be
// fixed automatically by InstancedNode.clone() method.
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch( CloneNotSupportedException e ) {
throw new RuntimeException("Error cloning control", e);
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.node = cloner.clone(node);
}
public void setSpatial(Spatial spatial){
}
public void update(float tpf){
}
public void render(RenderManager rm, ViewPort vp) {
node.renderFromControl();
}
public void write(JmeExporter ex) throws IOException {
}
public void read(JmeImporter im) throws IOException {
}
}
protected InstancedNodeControl control;
protected HashMap<Geometry, InstancedGeometry> igByGeom
protected HashMap<Geometry, InstancedGeometry> igByGeom
= new HashMap<Geometry, InstancedGeometry>();
private InstanceTypeKey lookUp = new InstanceTypeKey();
private HashMap<InstanceTypeKey, InstancedGeometry> instancesMap =
private HashMap<InstanceTypeKey, InstancedGeometry> instancesMap =
new HashMap<InstanceTypeKey, InstancedGeometry>();
public InstancedNode() {
super();
// NOTE: since we are deserializing,
// the control is going to be added automatically here.
}
public InstancedNode(String name) {
super(name);
control = new InstancedNodeControl(this);
addControl(control);
}
private void renderFromControl() {
for (InstancedGeometry ig : instancesMap.values()) {
ig.updateInstances();
@ -191,7 +223,7 @@ public class InstancedNode extends GeometryGroupNode {
return ig;
}
private void addToInstancedGeometry(Geometry geom) {
Material material = geom.getMaterial();
MatParam param = material.getParam("UseInstancing");
@ -200,20 +232,20 @@ public class InstancedNode extends GeometryGroupNode {
+ "parameter to true on the material prior "
+ "to adding it to InstancedNode");
}
InstancedGeometry ig = lookUpByGeometry(geom);
igByGeom.put(geom, ig);
geom.associateWithGroupNode(this, 0);
ig.addInstance(geom);
}
private void removeFromInstancedGeometry(Geometry geom) {
InstancedGeometry ig = igByGeom.remove(geom);
if (ig != null) {
ig.deleteInstance(geom);
}
}
private void relocateInInstancedGeometry(Geometry geom) {
InstancedGeometry oldIG = igByGeom.get(geom);
InstancedGeometry newIG = lookUpByGeometry(geom);
@ -226,7 +258,7 @@ public class InstancedNode extends GeometryGroupNode {
igByGeom.put(geom, newIG);
}
}
private void ungroupSceneGraph(Spatial s) {
if (s instanceof Node) {
for (Spatial sp : ((Node) s).getChildren()) {
@ -237,14 +269,14 @@ public class InstancedNode extends GeometryGroupNode {
if (g.isGrouped()) {
// Will invoke onGeometryUnassociated automatically.
g.unassociateFromGroupNode();
if (InstancedNode.getGeometryStartIndex(g) != -1) {
throw new AssertionError();
}
}
}
}
@Override
public Spatial detachChildAt(int index) {
Spatial s = super.detachChildAt(index);
@ -253,7 +285,7 @@ public class InstancedNode extends GeometryGroupNode {
}
return s;
}
private void instance(Spatial n) {
if (n instanceof Geometry) {
Geometry g = (Geometry) n;
@ -269,20 +301,20 @@ public class InstancedNode extends GeometryGroupNode {
}
}
}
public void instance() {
instance(this);
}
@Override
public Node clone() {
return clone(true);
}
@Override
public Node clone(boolean cloneMaterials) {
InstancedNode clone = (InstancedNode)super.clone(cloneMaterials);
if (instancesMap.size() > 0) {
// Remove all instanced geometries from the clone
for (int i = 0; i < clone.children.size(); i++) {
@ -296,7 +328,7 @@ public class InstancedNode extends GeometryGroupNode {
}
}
}
// remove original control from the clone
clone.controls.remove(this.control);
@ -307,12 +339,35 @@ public class InstancedNode extends GeometryGroupNode {
clone.lookUp = new InstanceTypeKey();
clone.igByGeom = new HashMap<Geometry, InstancedGeometry>();
clone.instancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
clone.instance();
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.control = cloner.clone(control);
this.lookUp = cloner.clone(lookUp);
HashMap<Geometry, InstancedGeometry> newIgByGeom = new HashMap<Geometry, InstancedGeometry>();
for( Map.Entry<Geometry, InstancedGeometry> e : igByGeom.entrySet() ) {
newIgByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
}
this.igByGeom = newIgByGeom;
HashMap<InstanceTypeKey, InstancedGeometry> newInstancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
for( Map.Entry<InstanceTypeKey, InstancedGeometry> e : instancesMap.entrySet() ) {
newInstancesMap.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
}
this.instancesMap = newInstancesMap;
}
@Override
public void onTransformChange(Geometry geom) {
// Handled automatically

@ -37,6 +37,7 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector4f;
import com.jme3.post.Filter;
@ -44,6 +45,7 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import java.io.IOException;
/**
@ -74,6 +76,9 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");
this.shadowRenderer = shadowRenderer;
this.shadowRenderer.setPostShadowMaterial(material);
//this is legacy setting for shadows with backface shadows
this.shadowRenderer.setRenderBackFacesShadows(true);
}
@Override
@ -126,7 +131,7 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
/**
* How far the shadows are rendered in the view
*
* @see setShadowZExtend(float zFar)
* @see #setShadowZExtend(float zFar)
* @return shadowZExtend
*/
public float getShadowZExtend() {
@ -248,6 +253,46 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
shadowRenderer.setEdgeFilteringMode(filterMode);
}
/**
*
* !! WARNING !! this parameter is defaulted to true for the ShadowFilter.
* Setting it to true, may produce edges artifacts on shadows. *
*
* Set to true if you want back faces shadows on geometries.
* Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
*
* Setting this parameter will override this parameter for ALL materials in the scene.
* This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
* You can modify them by using {@link #getPreShadowForcedRenderState()}
*
* If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead
* of the shadow filter.
*
* @param renderBackFacesShadows true or false.
*/
public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows);
}
/**
* if this filter renders back faces shadows
* @return true if this filter renders back faces shadows
*/
public boolean isRenderBackFacesShadows() {
return shadowRenderer.isRenderBackFacesShadows();
}
/**
* returns the pre shadows pass render state.
* use it to adjust the RenderState parameters of the pre shadow pass.
* Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState
* @return the pre shadow render state.
*/
public RenderState getPreShadowForcedRenderState() {
return shadowRenderer.getPreShadowForcedRenderState();
}
/**
* returns the the edge filtering mode
*

@ -31,10 +31,6 @@
*/
package com.jme3.shadow;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.jme3.asset.AssetManager;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
@ -42,6 +38,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector2f;
@ -67,6 +64,10 @@ import com.jme3.texture.Texture.ShadowCompareMode;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* abstract shadow renderer that holds commons feature to have for a shadow
* renderer
@ -92,6 +93,9 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear;
protected CompareMode shadowCompareMode = CompareMode.Hardware;
protected Picture[] dispPic;
protected RenderState forcedRenderState = new RenderState();
protected Boolean renderBackFacesShadows;
/**
* true if the fallback material should be used, otherwise false
*/
@ -181,6 +185,14 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
setShadowCompareMode(shadowCompareMode);
setEdgeFilteringMode(edgeFilteringMode);
setShadowIntensity(shadowIntensity);
initForcedRenderState();
}
protected void initForcedRenderState() {
forcedRenderState.setFaceCullMode(RenderState.FaceCullMode.Front);
forcedRenderState.setColorWrite(false);
forcedRenderState.setDepthWrite(true);
forcedRenderState.setDepthTest(true);
}
/**
@ -356,9 +368,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
* rendered in the shadow map
*
* @param shadowMapIndex the index of the shadow map being rendered
* @param sceneOccluders the occluders of the whole scene
* @param sceneReceivers the receivers of the whole scene
* @param shadowMapOcculders
* @param shadowMapOccluders the list of occluders
* @return
*/
protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders);
@ -425,9 +435,11 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]);
renderManager.getRenderer().clearBuffers(true, true, true);
renderManager.setForcedRenderState(forcedRenderState);
// render shadow casters to shadow map
viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true);
renderManager.setForcedRenderState(null);
}
boolean debugfrustums = false;
@ -535,18 +547,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
private void setMatParams(GeometryList l) {
//iteration throught all the geometries of the list to gather the materials
matCache.clear();
for (int i = 0; i < l.size(); i++) {
Material mat = l.get(i).getMaterial();
//checking if the material has the post technique and adding it to the material cache
if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
if (!matCache.contains(mat)) {
matCache.add(mat);
}
} else {
needsfallBackMaterial = true;
}
}
buildMatCache(l);
//iterating through the mat cache and setting the parameters
for (Material mat : matCache) {
@ -566,6 +567,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
if (fadeInfo != null) {
mat.setVector2("FadeInfo", fadeInfo);
}
if(renderBackFacesShadows != null){
mat.setBoolean("BackfaceShadows", renderBackFacesShadows);
}
setMaterialParameters(mat);
}
@ -577,6 +582,21 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
}
private void buildMatCache(GeometryList l) {
matCache.clear();
for (int i = 0; i < l.size(); i++) {
Material mat = l.get(i).getMaterial();
//checking if the material has the post technique and adding it to the material cache
if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
if (!matCache.contains(mat)) {
matCache.add(mat);
}
} else {
needsfallBackMaterial = true;
}
}
}
/**
* for internal use only
*/
@ -587,7 +607,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]);
}
if (fadeInfo != null) {
postshadowMat.setVector2("FadeInfo", fadeInfo);
postshadowMat.setVector2("FadeInfo", fadeInfo);
}
if(renderBackFacesShadows != null){
postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows);
}
}
@ -730,6 +753,48 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
@Deprecated
public void setFlushQueues(boolean flushQueues) {}
/**
* returns the pre shadows pass render state.
* use it to adjust the RenderState parameters of the pre shadow pass.
* Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState
* @return the pre shadow render state.
*/
public RenderState getPreShadowForcedRenderState() {
return forcedRenderState;
}
/**
* Set to true if you want back faces shadows on geometries.
* Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
*
* Also note that setting this parameter will override this parameter for ALL materials in the scene.
* You can alternatively change this parameter on a single material using {@link Material#setBoolean(String, boolean)}
*
* This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
* You can modify them by using {@link #getPreShadowForcedRenderState()}
*
* @param renderBackFacesShadows true or false.
*/
public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
this.renderBackFacesShadows = renderBackFacesShadows;
if(renderBackFacesShadows) {
getPreShadowForcedRenderState().setPolyOffset(5, 3);
getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
}else{
getPreShadowForcedRenderState().setPolyOffset(0, 0);
getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front);
}
}
/**
* if this processor renders back faces shadows
* @return true if this processor renders back faces shadows
*/
public boolean isRenderBackFacesShadows() {
return renderBackFacesShadows != null?renderBackFacesShadows:false;
}
/**
* De-serialize this instance, for example when loading from a J3O file.
*

@ -215,6 +215,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
@Override
protected void setMaterialParameters(Material material) {
material.setColor("Splits", splits);
material.setVector3("LightDir", light.getDirection());
if (fadeInfo != null) {
material.setVector2("FadeInfo", fadeInfo);
}
@ -224,6 +225,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
protected void clearMaterialParameters(Material material) {
material.clearParam("Splits");
material.clearParam("FadeInfo");
material.clearParam("LightDir");
}
/**

@ -157,6 +157,8 @@ public abstract class JmeSystemDelegate {
return false;
} else if (arch.equals("aarch64")) {
return true;
} else if (arch.equals("armv7") || arch.equals("armv7l")) {
return false;
} else if (arch.equals("arm")) {
return false;
} else {

@ -32,19 +32,21 @@
package com.jme3.util;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Similar to a {@link Map} except that ints are used as keys.
*
*
* Taken from <a href="http://code.google.com/p/skorpios/">http://code.google.com/p/skorpios/</a>
*
* @author Nate
*
* @author Nate
*/
public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable, JmeCloneable {
private Entry[] table;
private final float loadFactor;
private int size, mask, capacity, threshold;
@ -93,6 +95,26 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
return null;
}
/**
* 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.table = cloner.clone(table);
}
public boolean containsValue(Object value) {
Entry[] table = this.table;
for (int i = table.length; i-- > 0;){
@ -228,7 +250,7 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
idx = 0;
el = 0;
}
public boolean hasNext() {
return el < size;
}
@ -255,20 +277,20 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
// the entry was null. find another non-null entry.
cur = table[++idx];
} while (cur == null);
Entry e = cur;
cur = cur.next;
el ++;
return e;
}
public void remove() {
}
}
public static final class Entry<T> implements Cloneable {
public static final class Entry<T> implements Cloneable, JmeCloneable {
final int key;
T value;
@ -303,5 +325,20 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
}
return null;
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields( Cloner cloner, Object original ) {
this.value = cloner.clone(value);
this.next = cloner.clone(next);
}
}
}

@ -43,7 +43,7 @@ import java.util.*;
* the list is changing.</p>
*
* <p>All modifications, including set() operations will cause a copy of the
* data to be created that replaces the old version. Because this list is
* data to be created that replaces the old version. Because this list is
* not designed for threading concurrency it further optimizes the "many modifications"
* case by buffering them as a normal ArrayList until the next time the contents
* are accessed.</p>
@ -63,16 +63,16 @@ import java.util.*;
* Even after ListIterator.remove() or Iterator.remove() is called, this change
* is not reflected in the iterator instance as it is still refering to its
* original snapshot.
* </ul>
* </ul>
*
* @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
// example, the default iterator() method will not work for
// example, the default iterator() method will not work for
// this list.
// Note: given the particular use-cases this was intended,
@ -81,30 +81,48 @@ public class SafeArrayList<E> implements List<E> {
// SafeArrayList-specific methods could then be exposed
// for the classes like Node and Spatial to use to manage
// the list. This was the callers couldn't remove a child
// without it being detached properly, for example.
// without it being detached properly, for example.
private Class<E> elementType;
private Class<E> elementType;
private List<E> buffer;
private E[] backingArray;
private int size = 0;
public SafeArrayList(Class<E> elementType) {
this.elementType = elementType;
this.elementType = elementType;
}
public SafeArrayList(Class<E> elementType, Collection<? extends E> c) {
this.elementType = elementType;
this.elementType = elementType;
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);
return (T[])java.lang.reflect.Array.newInstance(type, size);
}
protected final E[] createArray(int size) {
return createArray(elementType, size);
return createArray(elementType, size);
}
/**
* Returns a current snapshot of this List's backing array that
* is guaranteed not to change through further List manipulation.
@ -114,10 +132,10 @@ public class SafeArrayList<E> implements List<E> {
public final E[] getArray() {
if( backingArray != null )
return backingArray;
if( buffer == null ) {
backingArray = createArray(0);
} else {
} else {
// Only keep the array or the buffer but never both at
// the same time. 1) it saves space, 2) it keeps the rest
// of the code safer.
@ -126,35 +144,35 @@ public class SafeArrayList<E> implements List<E> {
}
return backingArray;
}
protected final List<E> getBuffer() {
if( buffer != null )
return buffer;
if( backingArray == null ) {
buffer = new ArrayList();
} else {
} else {
// Only keep the array or the buffer but never both at
// the same time. 1) it saves space, 2) it keeps the rest
// of the code safer.
// of the code safer.
buffer = new ArrayList( Arrays.asList(backingArray) );
backingArray = null;
}
return buffer;
}
public final int size() {
return size;
return size;
}
public final boolean isEmpty() {
return size == 0;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public Iterator<E> iterator() {
return listIterator();
}
@ -162,70 +180,70 @@ public class SafeArrayList<E> implements List<E> {
public Object[] toArray() {
return getArray();
}
public <T> T[] toArray(T[] a) {
E[] array = getArray();
if (a.length < array.length) {
return (T[])Arrays.copyOf(array, array.length, a.getClass());
}
}
System.arraycopy( array, 0, a, 0, array.length );
if (a.length > array.length) {
a[array.length] = null;
}
return a;
}
public boolean add(E e) {
boolean result = getBuffer().add(e);
size = getBuffer().size();
return result;
}
public boolean remove(Object o) {
boolean result = getBuffer().remove(o);
size = getBuffer().size();
return result;
}
public boolean containsAll(Collection<?> c) {
return Arrays.asList(getArray()).containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
boolean result = getBuffer().addAll(c);
size = getBuffer().size();
return result;
}
public boolean addAll(int index, Collection<? extends E> c) {
boolean result = getBuffer().addAll(index, c);
size = getBuffer().size();
return result;
}
public boolean removeAll(Collection<?> c) {
boolean result = getBuffer().removeAll(c);
size = getBuffer().size();
return result;
}
public boolean retainAll(Collection<?> c) {
boolean result = getBuffer().retainAll(c);
size = getBuffer().size();
return result;
}
public void clear() {
getBuffer().clear();
size = 0;
}
public boolean equals(Object o) {
if( o == this )
if( o == this )
return true;
if( !(o instanceof List) ) //covers null too
return false;
@ -240,9 +258,9 @@ public class SafeArrayList<E> implements List<E> {
if( o1 == null || !o1.equals(o2) )
return false;
}
return !(i1.hasNext() || !i2.hasNext());
return !(i1.hasNext() || !i2.hasNext());
}
public int hashCode() {
// Exactly the hash code described in the List interface, basically
E[] array = getArray();
@ -252,30 +270,30 @@ public class SafeArrayList<E> implements List<E> {
}
return result;
}
public final E get(int index) {
if( backingArray != null )
return backingArray[index];
if( buffer != null )
return buffer.get(index);
throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" );
throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" );
}
public E set(int index, E element) {
return getBuffer().set(index, element);
}
public void add(int index, E element) {
getBuffer().add(index, element);
size = getBuffer().size();
}
public E remove(int index) {
E result = getBuffer().remove(index);
size = getBuffer().size();
return result;
}
public int indexOf(Object o) {
E[] array = getArray();
for( int i = 0; i < array.length; i++ ) {
@ -289,7 +307,7 @@ public class SafeArrayList<E> implements List<E> {
}
return -1;
}
public int lastIndexOf(Object o) {
E[] array = getArray();
for( int i = array.length - 1; i >= 0; i-- ) {
@ -303,29 +321,29 @@ public class SafeArrayList<E> implements List<E> {
}
return -1;
}
public ListIterator<E> listIterator() {
return new ArrayIterator<E>(getArray(), 0);
}
public ListIterator<E> listIterator(int index) {
return new ArrayIterator<E>(getArray(), index);
}
public List<E> subList(int fromIndex, int toIndex) {
// So far JME doesn't use subList that I can see so I'm nerfing it.
List<E> raw = Arrays.asList(getArray()).subList(fromIndex, toIndex);
return Collections.unmodifiableList(raw);
}
public String toString() {
E[] array = getArray();
if( array.length == 0 ) {
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append('[');
for( int i = 0; i < array.length; i++ ) {
@ -337,63 +355,63 @@ public class SafeArrayList<E> implements List<E> {
sb.append(']');
return sb.toString();
}
protected class ArrayIterator<E> implements ListIterator<E> {
private E[] array;
private int next;
private int lastReturned;
protected ArrayIterator( E[] array, int index ) {
this.array = array;
this.next = index;
this.lastReturned = -1;
}
public boolean hasNext() {
return next != array.length;
}
public E next() {
if( !hasNext() )
throw new NoSuchElementException();
lastReturned = next++;
return array[lastReturned];
}
public boolean hasPrevious() {
return next != 0;
}
return next != 0;
}
public E previous() {
if( !hasPrevious() )
throw new NoSuchElementException();
lastReturned = --next;
return array[lastReturned];
}
public int nextIndex() {
return next;
return next;
}
public int previousIndex() {
return next - 1;
}
public void remove() {
// This operation is not so easy to do but we will fake it.
// The issue is that the backing list could be completely
// different than the one this iterator is a snapshot of.
// We'll just remove(element) which in most cases will be
// We'll just remove(element) which in most cases will be
// correct. If the list had earlier .equals() equivalent
// elements then we'll remove one of those instead. Either
// way, none of those changes are reflected in this iterator.
SafeArrayList.this.remove( array[lastReturned] );
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}

@ -0,0 +1,81 @@
/*
* 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 com.jme3.util.clone;
/**
* Provides custom cloning for a particular object type. Once
* registered with the Cloner, this function object will be called twice
* for any cloned object that matches the class for which it was registered.
* It will first call cloneObject() to shallow clone the object and then call
* cloneFields() to deep clone the object's values.
*
* <p>This two step process is important because this is what allows
* circular references in the cloned object graph.</p>
*
* @author Paul Speed
*/
public interface CloneFunction<T> {
/**
* Performs a shallow clone of the specified object. This is similar
* to the JmeCloneable.clone() method in semantics and is the first part
* of a two part cloning process. Once the shallow clone is created, it
* is cached and CloneFunction.cloneFields() is called. In this way,
* the CloneFunction interface can completely take over the JmeCloneable
* style cloning for an object that doesn't otherwise implement that interface.
*
* @param cloner The cloner performing the cloning operation.
* @param original The original object that needs to be cloned.
*/
public T cloneObject( Cloner cloner, T original );
/**
* Performs a deep clone of the specified clone's fields. This is similar
* to the JmeCloneable.cloneFields() method in semantics and is the second part
* of a two part cloning process. Once the shallow clone is created, it
* is cached and CloneFunction.cloneFields() is called. In this way,
* the CloneFunction interface can completely take over the JmeCloneable
* style cloning for an object that doesn't otherwise implement that interface.
*
* @param cloner The cloner performing the cloning operation.
* @param clone The clone previously returned from cloneObject().
* @param original The original object that was cloned. This is provided for
* the very special case where field cloning needs to refer to
* the original object. Mostly the necessary fields should already
* be on the clone.
*/
public void cloneFields( Cloner cloner, T clone, T original );
}

@ -0,0 +1,412 @@
/*
* 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 com.jme3.util.clone;
import java.lang.reflect.Array;
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;
/**
* A deep clone utility that provides similar object-graph-preserving
* qualities to typical serialization schemes. An internal registry
* of cloned objects is kept to be used by other objects in the deep
* clone process that implement JmeCloneable.
*
* <p>By default, objects that do not implement JmeCloneable will
* be treated like normal Java Cloneable objects. If the object does
* not implement the JmeCloneable or the regular JDK Cloneable interfaces
* AND has no special handling defined then an IllegalArgumentException
* will be thrown.</p>
*
* <p>Enhanced object cloning is done in a two step process. First,
* the object is cloned using the normal Java clone() method and stored
* in the clone registry. After that, if it implements JmeCloneable then
* its cloneFields() method is called to deep clone any of the fields.
* This two step process has a few benefits. First, it means that objects
* can easily have a regular shallow clone implementation just like any
* normal Java objects. Second, the deep cloning of fields happens after
* creation wich means that the clone is available to future field cloning
* to resolve circular references.</p>
*
* <p>Similar to Java serialization, the handling of specific object
* types can be customized. This allows certain objects to be cloned gracefully
* even if they aren't normally Cloneable. This can also be used as a
* sort of filter to keep certain types of objects from being cloned.
* (For example, adding the IdentityCloneFunction for Mesh.class would cause
* all mesh instances to be shared with the original object graph.)</p>
*
* <p>By default, the Cloner registers serveral default clone functions
* as follows:</p>
* <ul>
* <li>java.util.ArrayList: ListCloneFunction
* <li>java.util.LinkedList: ListCloneFunction
* <li>java.util.concurrent.CopyOnWriteArrayList: ListCloneFunction
* <li>java.util.Vector: ListCloneFunction
* <li>java.util.Stack: ListCloneFunction
* <li>com.jme3.util.SafeArrayList: ListCloneFunction
* </ul>
*
* <p>Usage:</p>
* <pre>
* // Example 1: using an instantiated, reusable cloner.
* Cloner cloner = new Cloner();
* Foo fooClone = cloner.clone(foo);
* cloner.clearIndex(); // prepare it for reuse
* Foo fooClone2 = cloner.clone(foo);
*
* // Example 2: using the utility method that self-instantiates a temporary cloner.
* Foo fooClone = Cloner.deepClone(foo);
*
* </pre>
*
* @author Paul Speed
*/
public class Cloner {
static Logger log = Logger.getLogger(Cloner.class.getName());
/**
* Keeps track of the objects that have been cloned so far.
*/
private IdentityHashMap<Object, Object> index = new IdentityHashMap<Object, Object>();
/**
* Custom functions for cloning objects.
*/
private Map<Class, CloneFunction> functions = new HashMap<Class, CloneFunction>();
/**
* Cache the clone methods once for all cloners.
*/
private static final Map<Class, Method> methodCache = new ConcurrentHashMap<>();
/**
* Creates a new cloner with only default clone functions and an empty
* object index.
*/
public Cloner() {
// Register some standard types
ListCloneFunction listFunction = new ListCloneFunction();
functions.put(java.util.ArrayList.class, listFunction);
functions.put(java.util.LinkedList.class, listFunction);
functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction);
functions.put(java.util.Vector.class, listFunction);
functions.put(java.util.Stack.class, listFunction);
functions.put(com.jme3.util.SafeArrayList.class, listFunction);
}
/**
* Convenience utility function that creates a new Cloner, uses it to
* deep clone the object, and then returns the result.
*/
public static <T> T deepClone( T object ) {
return new Cloner().clone(object);
}
/**
* Deeps clones the specified object, reusing previous clones when possible.
*
* <p>Object cloning priority works as follows:</p>
* <ul>
* <li>If the object has already been cloned then its clone is returned.
* <li>If there is a custom CloneFunction then it is called to clone the object.
* <li>If the object implements Cloneable then its clone() method is called, arrays are
* deep cloned with entries passing through clone().
* <li>If the object implements JmeCloneable then its cloneFields() method is called on the
* clone.
* <li>Else an IllegalArgumentException is thrown.
* </ul>
*
* Note: objects returned by this method may not have yet had their cloneField()
* method called.
*/
public <T> T clone( T object ) {
return clone(object, true);
}
/**
* Internal method to work around a Java generics typing issue by
* isolating the 'bad' case into a method with suppressed warnings.
*/
@SuppressWarnings("unchecked")
private <T> Class<T> objectClass( T object ) {
// This should be 100% allowed without a cast but Java generics
// is not that smart sometimes.
// Wrapping it in a method at least isolates the warning suppression
return (Class<T>)object.getClass();
}
/**
* Deeps clones the specified object, reusing previous clones when possible.
*
* <p>Object cloning priority works as follows:</p>
* <ul>
* <li>If the object has already been cloned then its clone is returned.
* <li>If useFunctions is true and there is a custom CloneFunction then it is
* called to clone the object.
* <li>If the object implements Cloneable then its clone() method is called, arrays are
* deep cloned with entries passing through clone().
* <li>If the object implements JmeCloneable then its cloneFields() method is called on the
* clone.
* <li>Else an IllegalArgumentException is thrown.
* </ul>
*
* <p>The abililty to selectively use clone functions is useful when
* being called from a clone function.</p>
*
* Note: objects returned by this method may not have yet had their cloneField()
* 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( log.isLoggable(Level.FINER) ) {
log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object)
+ " as cached:" + clone.getClass() + "@" + System.identityHashCode(clone));
}
return type.cast(clone);
}
// See if there is a custom function... that trumps everything.
CloneFunction<T> f = getCloneFunction(type);
if( f != null ) {
T result = f.cloneObject(this, object);
// Store the object in the identity map so that any circular references
// are resolvable.
index.put(object, result);
// 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;
}
if( object.getClass().isArray() ) {
// Perform an array clone
clone = arrayClone(object);
// Array clone already indexes the clone
} else if( object instanceof JmeCloneable ) {
// Use the two-step cloning semantics
clone = ((JmeCloneable)object).jmeClone();
// Store the object in the identity map so that any circular references
// are resolvable
index.put(object, clone);
((JmeCloneable)clone).cloneFields(this, object);
} else if( object instanceof Cloneable ) {
// Perform a regular Java shallow clone
try {
clone = javaClone(object);
} catch( CloneNotSupportedException e ) {
throw new IllegalArgumentException("Object is not cloneable, type:" + type, e);
}
// Store the object in the identity map so that any circular references
// are resolvable
index.put(object, clone);
} else {
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 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 ) {
functions.remove(type);
} else {
functions.put(type, function);
}
}
/**
* Returns a previously registered clone function for the specified type or null
* if there is no custom clone function for the type.
*/
@SuppressWarnings("unchecked")
public <T> CloneFunction<T> getCloneFunction( Class<T> 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);
}
/**
* Clears the object index allowing the cloner to be reused for a brand new
* cloning operation.
*/
public void clearIndex() {
index.clear();
}
/**
* Performs a raw shallow Java clone using reflection. This call does NOT
* check against the clone index and so will return new objects every time
* it is called. That's because these are shallow clones and have not (and may
* not ever, depending on the caller) get resolved.
*
* <p>This method is provided as a convenient way for CloneFunctions to call
* clone() and objects without necessarily knowing their real type.</p>
*/
public <T> T javaClone( T object ) throws CloneNotSupportedException {
Method m = methodCache.get(object.getClass());
if( m == null ) {
try {
// Lookup the method and cache it
m = object.getClass().getMethod("clone");
} catch( NoSuchMethodException e ) {
throw new CloneNotSupportedException("No public clone method found for:" + object.getClass());
}
methodCache.put(object.getClass(), m);
// Note: yes we might cache the method twice... but so what?
}
try {
Class<? extends T> type = objectClass(object);
return type.cast(m.invoke(object));
} catch( IllegalAccessException | InvocationTargetException e ) {
throw new RuntimeException("Error cloning object of type:" + object.getClass(), e);
}
}
/**
* Clones a primitive array by coping it and clones an object
* array by coping it and then running each of its values through
* Cloner.clone().
*/
protected <T> T arrayClone( T object ) {
// Java doesn't support the cloning of arrays through reflection unless
// you open access to Object's protected clone array... which requires
// elevated privileges. So we will do a work-around that is slightly less
// elegant.
// This should be 100% allowed without a case but Java generics
// is not that smart
Class<T> type = objectClass(object);
Class elementType = type.getComponentType();
int size = Array.getLength(object);
Object clone = Array.newInstance(elementType, size);
// Store the clone for later lookups
index.put(object, clone);
if( elementType.isPrimitive() ) {
// Then our job is a bit easier
System.arraycopy(object, 0, clone, 0, size);
} else {
// Else it's an object array so we'll clone it and its children
for( int i = 0; i < size; i++ ) {
Object element = clone(Array.get(object, i));
Array.set(clone, i, element);
}
}
return type.cast(clone);
}
}

@ -0,0 +1,58 @@
/*
* 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 com.jme3.util.clone;
/**
* A CloneFunction implementation that simply returns the
* the passed object without cloning it. This is useful for
* forcing some object types (like Meshes) to be shared between
* the original and cloned object graph.
*
* @author Paul Speed
*/
public class IdentityCloneFunction<T> implements CloneFunction<T> {
/**
* Returns the object directly.
*/
public T cloneObject( Cloner cloner, T object ) {
return object;
}
/**
* Does nothing.
*/
public void cloneFields( Cloner cloner, T clone, T object ) {
}
}

@ -0,0 +1,99 @@
/*
* 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 com.jme3.util.clone;
/**
* Indicates an object that wishes to more actively participate in the
* two-part deep copying process provided by the Cloner. Objects implementing
* this interface can access the already cloned object graph to resolve
* their local dependencies in a way that will be equivalent to the
* original object graph. In other words, if two objects in the graph
* share the same target reference then the cloned version will share
* the cloned reference.
*
* <p>For example, if an object wishes to deep clone one of its fields
* then it will call cloner.clone(object) instead of object.clone().
* The cloner will keep track of any clones already created for 'object'
* and return that instead of a new clone.</p>
*
* <p>Cloning of a JmeCloneable object is done in two parts. First,
* the standard Java clone() method is called to create a shallow clone
* of the object. Second, the cloner wil lcall the cloneFields() method
* to let the object deep clone any of its fields that should be cloned.</p>
*
* <p>This two part process is necessary to facilitate circular references.
* When an object calls cloner.clone() during its cloneFields() method, it
* may get only a shallow copy that will be filled in later.</p>
*
* @author Paul Speed
*/
public interface JmeCloneable extends Cloneable {
/**
* Performs a regular shallow clone of the object. Some fields
* may also be cloned but generally only if they will never be
* shared with other objects. (For example, local Vector3fs and so on.)
*
* <p>This method is separate from the regular clone() method
* so that objects might still maintain their own regular java clone()
* semantics (perhaps even using Cloner for those methods). However,
* because Java's clone() has specific features in the sense of Object's
* clone() implementation, it's usually best to have some path for
* subclasses to bypass the public clone() method that might be cloning
* fields and instead get at the superclass protected clone() methods.
* For example, through super.jmeClone() or another protected clone
* method that some base class eventually calls super.clone() in.</p>
*/
public Object jmeClone();
/**
* Implemented to perform deep cloning for this object, resolving
* local cloned references using the specified cloner. The object
* can call cloner.clone(fieldValue) to deep clone any of its fields.
*
* <p>Note: during normal clone operations the original object
* will not be needed as the clone has already had all of the fields
* shallow copied.</p>
*
* @param cloner The cloner that is performing the cloning operation. The
* cloneFields method can call back into the cloner to make
* clones if its subordinate fields.
* @param original The original object from which this object was cloned.
* This is provided for the very rare case that this object needs
* to refer to its original for some reason. In general, all of
* the relevant values should have been transferred during the
* shallow clone and this object need merely clone what it wants.
*/
public void cloneFields( Cloner cloner, Object original );
}

@ -0,0 +1,70 @@
/*
* 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 com.jme3.util.clone;
import java.util.List;
/**
* A CloneFunction implementation that deep clones a list by
* creating a new list and cloning its values using the cloner.
*
* @author Paul Speed
*/
public class ListCloneFunction<T extends List> implements CloneFunction<T> {
public T cloneObject( Cloner cloner, T object ) {
try {
T clone = cloner.javaClone(object);
return clone;
} catch( CloneNotSupportedException e ) {
throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e);
}
}
/**
* Clones the elements of the list.
*/
@SuppressWarnings("unchecked")
public void cloneFields( Cloner cloner, T clone, T object ) {
for( int i = 0; i < clone.size(); i++ ) {
// Need to clone the clones... because T might
// have done something special in its clone method that
// we will have to adhere to. For example, clone may have nulled
// out some things or whatever that might be implementation specific.
// At any rate, if it's a proper clone then the clone will already
// have shallow versions of the elements that we can clone.
clone.set(i, cloner.clone(clone.get(i)));
}
}
}

@ -113,6 +113,8 @@ MaterialDef Phong Lighting {
//For instancing
Boolean UseInstancing
Boolean BackfaceShadows: false
}
Technique {
@ -212,26 +214,19 @@ MaterialDef Phong Lighting {
INSTANCING : UseInstancing
}
ForcedRenderState {
FaceCull Off
DepthTest On
DepthWrite On
PolyOffset 5 3
ColorWrite Off
}
}
Technique PostShadow15{
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
NormalMatrix
}
Defines {
@ -245,6 +240,7 @@ MaterialDef Phong Lighting {
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
}
ForcedRenderState {
@ -263,6 +259,7 @@ MaterialDef Phong Lighting {
WorldMatrix
ViewProjectionMatrix
ViewMatrix
NormalMatrix
}
Defines {

@ -41,8 +41,7 @@ varying vec3 SpecularSum;
#ifdef NORMALMAP
uniform sampler2D m_NormalMap;
varying vec3 vTangent;
varying vec3 vBinormal;
varying vec4 vTangent;
#endif
varying vec3 vNormal;
@ -71,7 +70,7 @@ uniform float m_Shininess;
void main(){
#if !defined(VERTEX_LIGHTING)
#if defined(NORMALMAP)
mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
mat3 tbnMat = mat3(vTangent.xyz, vTangent.w * cross( (vNormal), (vTangent.xyz)), vNormal.xyz);
if (!gl_FrontFacing)
{

@ -39,8 +39,7 @@ attribute vec3 inNormal;
varying vec3 vPos;
#ifdef NORMALMAP
attribute vec4 inTangent;
varying vec3 vTangent;
varying vec3 vBinormal;
varying vec4 vTangent;
#endif
#else
#ifdef COLORRAMP
@ -104,8 +103,7 @@ void main(){
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
vTangent = TransformNormal(modelSpaceTan);
vBinormal = cross(wvNormal, vTangent)* inTangent.w;
vTangent = vec4(TransformNormal(modelSpaceTan).xyz,inTangent.w);
vNormal = wvNormal;
vPos = wvPosition;
#elif !defined(VERTEX_LIGHTING)

@ -51,6 +51,8 @@ MaterialDef Unshaded {
Float PCFEdge
Float ShadowMapSize
Boolean BackfaceShadows: true
}
Technique {
@ -147,8 +149,8 @@ MaterialDef Unshaded {
Technique PostShadow15{
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
@ -169,6 +171,7 @@ MaterialDef Unshaded {
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
}
ForcedRenderState {
@ -201,6 +204,7 @@ MaterialDef Unshaded {
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
}
ForcedRenderState {

@ -1,4 +1,5 @@
#import "Common/ShaderLib/Shadows.glsllib"
#import "Common/ShaderLib/GLSLCompat.glsllib"
#if defined(PSSM) || defined(FADE)
varying float shadowPosition;
@ -8,6 +9,9 @@ varying vec4 projCoord0;
varying vec4 projCoord1;
varying vec4 projCoord2;
varying vec4 projCoord3;
#ifndef BACKFACE_SHADOWS
varying float nDotL;
#endif
#ifdef POINTLIGHT
varying vec4 projCoord4;
@ -45,9 +49,15 @@ void main(){
if(alpha<=m_AlphaDiscardThreshold){
discard;
}
#endif
#ifndef BACKFACE_SHADOWS
if(nDotL > 0.0){
discard;
}
#endif
float shadow = 1.0;
#ifdef POINTLIGHT
@ -70,11 +80,11 @@ void main(){
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
#endif
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
}

@ -29,12 +29,14 @@ MaterialDef Post Shadow {
Float PCFEdge
Float ShadowMapSize
Boolean BackfaceShadows: false
}
Technique {
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
@ -49,6 +51,7 @@ MaterialDef Post Shadow {
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
BACKFACE_SHADOWS: BackfaceShadows
}
RenderState {
@ -75,6 +78,7 @@ MaterialDef Post Shadow {
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
BACKFACE_SHADOWS: BackfaceShadows
}
RenderState {

@ -1,11 +1,12 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/GLSLCompat.glsllib"
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform vec3 m_LightPos;
varying vec4 projCoord0;
varying vec4 projCoord1;
@ -15,12 +16,14 @@ varying vec4 projCoord3;
#ifdef POINTLIGHT
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
uniform vec3 m_LightPos;
varying vec4 projCoord4;
varying vec4 projCoord5;
varying vec4 worldPos;
#else
uniform vec3 m_LightDir;
#ifndef PSSM
uniform vec3 m_LightDir;
uniform vec3 m_LightPos;
varying float lightDot;
#endif
#endif
@ -28,12 +31,15 @@ varying vec4 projCoord3;
#if defined(PSSM) || defined(FADE)
varying float shadowPosition;
#endif
varying vec3 lightVec;
varying vec2 texCoord;
attribute vec3 inPosition;
#ifndef BACKFACE_SHADOWS
attribute vec3 inNormal;
varying float nDotL;
#endif
#ifdef DISCARD_ALPHA
attribute vec2 inTexCoord;
#endif
@ -51,16 +57,17 @@ void main(){
Skinning_Compute(modelSpacePos);
#endif
gl_Position = TransformWorldViewProjection(modelSpacePos);
vec3 lightDir;
#if defined(PSSM) || defined(FADE)
shadowPosition = gl_Position.z;
shadowPosition = gl_Position.z;
#endif
#ifndef POINTLIGHT
vec4 worldPos=vec4(0.0);
#endif
// get the vertex in world space
worldPos = g_WorldMatrix * modelSpacePos;
worldPos = TransformWorld(modelSpacePos);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;
@ -75,8 +82,21 @@ void main(){
projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
#else
#ifndef PSSM
vec3 lightDir = worldPos.xyz - m_LightPos;
//Spot light
lightDir = worldPos.xyz - m_LightPos;
lightDot = dot(m_LightDir,lightDir);
#endif
#endif
#ifndef BACKFACE_SHADOWS
vec3 normal = normalize(TransformWorld(vec4(inNormal,0.0))).xyz;
#ifdef POINTLIGHT
lightDir = worldPos.xyz - m_LightPos;
#else
#ifdef PSSM
lightDir = m_LightDir;
#endif
#endif
nDotL = dot(normal, lightDir);
#endif
}

@ -1,80 +0,0 @@
#import "Common/ShaderLib/Shadows15.glsllib"
out vec4 outFragColor;
#if defined(PSSM) || defined(FADE)
in float shadowPosition;
#endif
in vec4 projCoord0;
in vec4 projCoord1;
in vec4 projCoord2;
in vec4 projCoord3;
#ifdef POINTLIGHT
in vec4 projCoord4;
in vec4 projCoord5;
in vec4 worldPos;
uniform vec3 m_LightPos;
#else
#ifndef PSSM
in float lightDot;
#endif
#endif
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
uniform sampler2D m_ColorMap;
#else
uniform sampler2D m_DiffuseMap;
#endif
uniform float m_AlphaDiscardThreshold;
varying vec2 texCoord;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
void main(){
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
float alpha = texture2D(m_ColorMap,texCoord).a;
#else
float alpha = texture2D(m_DiffuseMap,texCoord).a;
#endif
if(alpha < m_AlphaDiscardThreshold){
discard;
}
#endif
float shadow = 1.0;
#ifdef POINTLIGHT
shadow = getPointLightShadows(worldPos, m_LightPos,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
#else
#ifdef PSSM
shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
projCoord0, projCoord1, projCoord2, projCoord3);
#else
//spotlight
if(lightDot < 0){
outFragColor = vec4(1.0);
return;
}
shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
#endif
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
#endif
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
outFragColor = vec4(shadow, shadow, shadow, 1.0);
}

@ -1,82 +0,0 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
out vec4 projCoord0;
out vec4 projCoord1;
out vec4 projCoord2;
out vec4 projCoord3;
#ifdef POINTLIGHT
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
out vec4 projCoord4;
out vec4 projCoord5;
out vec4 worldPos;
#else
#ifndef PSSM
uniform vec3 m_LightPos;
uniform vec3 m_LightDir;
out float lightDot;
#endif
#endif
#if defined(PSSM) || defined(FADE)
out float shadowPosition;
#endif
out vec3 lightVec;
out vec2 texCoord;
in vec3 inPosition;
#ifdef DISCARD_ALPHA
in vec2 inTexCoord;
#endif
const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
void main(){
vec4 modelSpacePos = vec4(inPosition, 1.0);
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif
gl_Position = TransformWorldViewProjection(modelSpacePos);
#if defined(PSSM) || defined(FADE)
shadowPosition = gl_Position.z;
#endif
#ifndef POINTLIGHT
vec4 worldPos=vec4(0.0);
#endif
// get the vertex in world space
worldPos = TransformWorld(modelSpacePos);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;
#endif
// populate the light view matrices array and convert vertex to light viewProj space
projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos;
projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos;
projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos;
projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos;
#ifdef POINTLIGHT
projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos;
projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
#else
#ifndef PSSM
vec3 lightDir = worldPos.xyz - m_LightPos;
lightDot = dot(m_LightDir,lightDir);
#endif
#endif
}

@ -18,14 +18,16 @@ uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform vec2 g_ResolutionInverse;
#ifdef POINTLIGHT
uniform vec3 m_LightPos;
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
#else
uniform vec3 m_LightDir;
#ifndef PSSM
uniform vec3 m_LightPos;
uniform vec3 m_LightDir;
uniform vec3 m_LightPos;
#endif
#endif
@ -39,6 +41,19 @@ vec3 getPosition(in float depth, in vec2 uv){
return pos.xyz / pos.w;
}
vec3 approximateNormal(in vec4 worldPos,in vec2 texCoord){
float step = g_ResolutionInverse.x ;
float stepy = g_ResolutionInverse.y ;
float depth2 = texture2D(m_DepthTexture,texCoord + vec2(step,-stepy)).r;
float depth3 = texture2D(m_DepthTexture,texCoord + vec2(-step,-stepy)).r;
vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,-stepy)),1.0);
vec4 worldPos3 = vec4(getPosition(depth3,texCoord + vec2(-step,-stepy)),1.0);
vec3 v1 = (worldPos - worldPos2).xyz;
vec3 v2 = (worldPos3 - worldPos2).xyz;
return normalize(cross(v1, v2));
}
void main(){
#if !defined( RENDER_SHADOWS )
gl_FragColor = texture2D(m_Texture,texCoord);
@ -48,6 +63,7 @@ void main(){
float depth = texture2D(m_DepthTexture,texCoord).r;
vec4 color = texture2D(m_Texture,texCoord);
//Discard shadow computation on the sky
if(depth == 1.0){
gl_FragColor = color;
@ -56,6 +72,19 @@ void main(){
// get the vertex in world space
vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
vec3 normal = approximateNormal(worldPos, texCoord);
vec3 lightDir;
#ifdef PSSM
lightDir = m_LightDir;
#else
lightDir = worldPos.xyz - m_LightPos;
#endif
float ndotl = dot(normal, lightDir);
if(ndotl > -0.0){
gl_FragColor = color;
return;
}
#if (!defined(POINTLIGHT) && !defined(PSSM))
vec3 lightDir = worldPos.xyz - m_LightPos;

@ -38,13 +38,15 @@ MaterialDef Post Shadow {
Texture2D Texture
Texture2D DepthTexture
Boolean BackfaceShadows: true
}
Technique {
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag
WorldParameters {
WorldParameters {
ResolutionInverse
}
Defines {
@ -59,7 +61,7 @@ MaterialDef Post Shadow {
POINTLIGHT : LightViewProjectionMatrix5
//if no shadow map don't render shadows
RENDER_SHADOWS : ShadowMap0
BACKFACE_SHADOWS : BackfaceShadows
}
}
@ -68,7 +70,8 @@ MaterialDef Post Shadow {
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag
WorldParameters {
WorldParameters {
ResolutionInverse
}
Defines {
@ -79,6 +82,7 @@ MaterialDef Post Shadow {
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
BACKFACE_SHADOWS : BackfaceShadows
}
}

@ -1,5 +1,5 @@
#import "Common/ShaderLib/MultiSample.glsllib"
#import "Common/ShaderLib/Shadows15.glsllib"
#import "Common/ShaderLib/Shadows.glsllib"
uniform COLORTEXTURE m_Texture;
@ -20,14 +20,16 @@ uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform vec2 g_ResolutionInverse;
#ifdef POINTLIGHT
uniform vec3 m_LightPos;
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
#else
uniform vec3 m_LightDir;
#ifndef PSSM
uniform vec3 m_LightPos;
uniform vec3 m_LightDir;
uniform vec3 m_LightPos;
#endif
#endif
@ -41,6 +43,23 @@ vec3 getPosition(in float depth, in vec2 uv){
return pos.xyz / pos.w;
}
#ifndef BACKFACE_SHADOWS
vec3 approximateNormal(in float depth,in vec4 worldPos,in vec2 texCoord, in int numSample){
float step = g_ResolutionInverse.x ;
float stepy = g_ResolutionInverse.y ;
float depth1 = fetchTextureSample(m_DepthTexture,texCoord + vec2(-step,stepy),numSample).r;
float depth2 = fetchTextureSample(m_DepthTexture,texCoord + vec2(step,stepy),numSample).r;
vec3 v1, v2;
vec4 worldPos1 = vec4(getPosition(depth1,texCoord + vec2(-step,stepy)),1.0);
vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,stepy)),1.0);
v1 = normalize((worldPos1 - worldPos)).xyz;
v2 = normalize((worldPos2 - worldPos)).xyz;
return normalize(cross(v2, v1));
}
#endif
vec4 main_multiSample(in int numSample){
float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r;
vec4 color = fetchTextureSample(m_Texture,texCoord,numSample);
@ -52,12 +71,27 @@ vec4 main_multiSample(in int numSample){
// get the vertex in world space
vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
vec3 lightDir;
#ifdef PSSM
lightDir = m_LightDir;
#else
lightDir = worldPos.xyz - m_LightPos;
#endif
#ifndef BACKFACE_SHADOWS
vec3 normal = approximateNormal(depth, worldPos, texCoord, numSample);
float ndotl = dot(normal, lightDir);
if(ndotl > 0.0){
return color;
}
#endif
#if (!defined(POINTLIGHT) && !defined(PSSM))
vec3 lightDir = worldPos.xyz - m_LightPos;
if( dot(m_LightDir,lightDir)<0){
return color;
}
if( dot(m_LightDir,lightDir)<0){
return color;
}
#endif
// populate the light view matrices array and convert vertex to light viewProj space

@ -1,22 +1,57 @@
#ifdef HARDWARE_SHADOWS
#define SHADOWMAP sampler2DShadow
#define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
#else
#define SHADOWMAP sampler2D
#define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
#endif
#if __VERSION__ >= 130
// Because gpu_shader5 is actually where those
// gather functions are declared to work on shadowmaps
#extension GL_ARB_gpu_shader5 : enable
#define IVEC2 ivec2
#ifdef HARDWARE_SHADOWS
#define SHADOWMAP sampler2DShadow
#define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
#define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
#define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
#else
#define SHADOWMAP sampler2D
#define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
#define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
#endif
#if FILTER_MODE == 0
#define GETSHADOW Shadow_DoShadowCompare
#define KERNEL 1.0
#elif FILTER_MODE == 1
#if FILTER_MODE == 0
#define GETSHADOW Shadow_Nearest
#define KERNEL 1.0
#elif FILTER_MODE == 1
#ifdef HARDWARE_SHADOWS
#define GETSHADOW Shadow_Nearest
#else
#define GETSHADOW Shadow_DoBilinear_2x2
#endif
#define KERNEL 1.0
#endif
#else
#define IVEC2 vec2
#ifdef HARDWARE_SHADOWS
#define GETSHADOW Shadow_DoShadowCompare
#define SHADOWMAP sampler2DShadow
#define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
#else
#define GETSHADOW Shadow_DoBilinear_2x2
#define SHADOWMAP sampler2D
#define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
#endif
#define KERNEL 1.0
#elif FILTER_MODE == 2
#if FILTER_MODE == 0
#define GETSHADOW Shadow_DoShadowCompare
#define KERNEL 1.0
#elif FILTER_MODE == 1
#ifdef HARDWARE_SHADOWS
#define GETSHADOW Shadow_DoShadowCompare
#else
#define GETSHADOW Shadow_DoBilinear_2x2
#endif
#define KERNEL 1.0
#endif
#endif
#if FILTER_MODE == 2
#define GETSHADOW Shadow_DoDither_2x2
#define KERNEL 1.0
#elif FILTER_MODE == 3
@ -30,14 +65,13 @@
#define KERNEL 8.0
#endif
uniform SHADOWMAP m_ShadowMap0;
uniform SHADOWMAP m_ShadowMap1;
uniform SHADOWMAP m_ShadowMap2;
uniform SHADOWMAP m_ShadowMap3;
#ifdef POINTLIGHT
uniform SHADOWMAP m_ShadowMap4;
uniform SHADOWMAP m_ShadowMap5;
uniform SHADOWMAP m_ShadowMap4;
uniform SHADOWMAP m_ShadowMap5;
#endif
#ifdef PSSM
@ -49,73 +83,91 @@ uniform float m_ShadowIntensity;
const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
float shadowBorderScale = 1.0;
float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){
vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
return SHADOWCOMPARE(tex, coord);
}
float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){
float Shadow_DoShadowCompare(in SHADOWMAP tex,in vec4 projCoord){
return SHADOWCOMPARE(tex, projCoord);
}
float Shadow_BorderCheck(vec2 coord){
float Shadow_BorderCheck(in vec2 coord){
// Fastest, "hack" method (uses 4-5 instructions)
vec4 t = vec4(coord.xy, 0.0, 1.0);
t = step(t.wwxy, t.xyzz);
return dot(t,t);
}
float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){
float Shadow_Nearest(in SHADOWMAP tex,in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0){
return 1.0;
}
return Shadow_DoShadowCompare(tex,projCoord);
return SHADOWCOMPARE(tex, projCoord);
}
float Shadow_DoShadowCompareOffset(in SHADOWMAP tex,in vec4 projCoord,in vec2 offset){
vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
return SHADOWCOMPARE(tex, coord);
}
float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){
float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
float shadow = 0.0;
vec2 o = mod(floor(gl_FragCoord.xy), 2.0);
shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, 1.5) + o);
shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, 1.5) + o);
shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o);
shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o);
shadow *= 0.25 ;
IVEC2 o = IVEC2(mod(floor(gl_FragCoord.xy), 2.0));
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, 1.5)+o));
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, 1.5)+o));
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, -0.5)+o));
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, -0.5)+o));
shadow *= 0.25;
return shadow;
}
float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){
float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
if (border > 0.0){
return 1.0;
}
vec4 gather = vec4(0.0);
gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
vec2 mx = mix( gather.xz, gather.yw, f.x );
return mix( mx.x, mx.y, f.y );
#if __VERSION__ >= 130
#ifdef GL_ARB_gpu_shader5
vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
gather = SHADOWGATHER(tex, coord);
#else
gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
#endif
#else
gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
#endif
vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
vec2 mx = mix( gather.wx, gather.zy, f.x );
return mix( mx.x, mx.y, f.y );
}
float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
float bound = KERNEL * 0.5 - 0.5;
bound *= PCFEDGE;
for (float y = -bound; y <= bound; y += PCFEDGE){
for (float x = -bound; x <= bound; x += PCFEDGE){
shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) +
border,
0.0, 1.0);
#if __VERSION__ < 130
shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + border, 0.0, 1.0);
#else
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, vec2(x,y));
#endif
}
}
@ -123,51 +175,51 @@ float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
return shadow;
}
//12 tap poisson disk
const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){
const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
if (border > 0.0){
return 1.0;
}
vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize);
shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize);
shadow = shadow * 0.08333333333;//this is divided by 12
return shadow;
}
vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
//this is divided by 12
return shadow * 0.08333333333;
}
#ifdef POINTLIGHT
float getPointLightShadows(vec4 worldPos,vec3 lightPos,
SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5,
vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){
#ifdef POINTLIGHT
float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
float shadow = 1.0;
vec3 vect = worldPos.xyz - lightPos;
vec3 absv= abs(vect);
@ -190,42 +242,41 @@ float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){
}else{
shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
}
}
}
return shadow;
}
#else
#ifdef PSSM
float getDirectionalLightShadows(vec4 splits,float shadowPosition,
SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,
vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){
float shadow = 1.0;
float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
float shadow = 1.0;
if(shadowPosition < splits.x){
shadow = GETSHADOW(shadowMap0, projCoord0 );
shadow = GETSHADOW(shadowMap0, projCoord0 );
}else if( shadowPosition < splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(shadowMap1, projCoord1);
shadow = GETSHADOW(shadowMap1, projCoord1);
}else if( shadowPosition < splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(shadowMap2, projCoord2);
shadow = GETSHADOW(shadowMap2, projCoord2);
}else if( shadowPosition < splits.w){
shadowBorderScale = 0.125;
shadow = GETSHADOW(shadowMap3, projCoord3);
shadow = GETSHADOW(shadowMap3, projCoord3);
}
return shadow;
}
#else
float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){
float shadow = 1.0;
float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){
float shadow = 1.0;
projCoord /= projCoord.w;
shadow = GETSHADOW(shadowMap, projCoord);
shadow = GETSHADOW(shadowMap,projCoord);
//a small falloff to make the shadow blend nicely into the not lighten
//we translate the texture coordinate value to a -1,1 range so the length
//we translate the texture coordinate value to a -1,1 range so the length
//of the texture coordinate vector is actually the radius of the lighten area on the ground
projCoord = projCoord * 2.0 - 1.0;
float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
}
#endif
#endif

@ -1,242 +0,0 @@
// Because gpu_shader5 is actually where those
// gather functions are declared to work on shadowmaps
#extension GL_ARB_gpu_shader5 : enable
#ifdef HARDWARE_SHADOWS
#define SHADOWMAP sampler2DShadow
#define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
#define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
#define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
#else
#define SHADOWMAP sampler2D
#define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
#define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
#endif
#if FILTER_MODE == 0
#define GETSHADOW Shadow_Nearest
#define KERNEL 1.0
#elif FILTER_MODE == 1
#ifdef HARDWARE_SHADOWS
#define GETSHADOW Shadow_Nearest
#else
#define GETSHADOW Shadow_DoBilinear_2x2
#endif
#define KERNEL 1.0
#elif FILTER_MODE == 2
#define GETSHADOW Shadow_DoDither_2x2
#define KERNEL 1.0
#elif FILTER_MODE == 3
#define GETSHADOW Shadow_DoPCF
#define KERNEL 4.0
#elif FILTER_MODE == 4
#define GETSHADOW Shadow_DoPCFPoisson
#define KERNEL 4.0
#elif FILTER_MODE == 5
#define GETSHADOW Shadow_DoPCF
#define KERNEL 8.0
#endif
uniform SHADOWMAP m_ShadowMap0;
uniform SHADOWMAP m_ShadowMap1;
uniform SHADOWMAP m_ShadowMap2;
uniform SHADOWMAP m_ShadowMap3;
#ifdef POINTLIGHT
uniform SHADOWMAP m_ShadowMap4;
uniform SHADOWMAP m_ShadowMap5;
#endif
#ifdef PSSM
uniform vec4 m_Splits;
#endif
uniform float m_ShadowIntensity;
const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
float shadowBorderScale = 1.0;
float Shadow_BorderCheck(in vec2 coord){
// Fastest, "hack" method (uses 4-5 instructions)
vec4 t = vec4(coord.xy, 0.0, 1.0);
t = step(t.wwxy, t.xyzz);
return dot(t,t);
}
float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0){
return 1.0;
}
return SHADOWCOMPARE(tex,projCoord);
}
float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
vec2 pixSize = pixSize2 * shadowBorderScale;
float shadow = 0.0;
ivec2 o = ivec2(mod(floor(gl_FragCoord.xy), 2.0));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, 1.5)+o), projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, 1.5)+o), projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, -0.5)+o), projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, -0.5)+o), projCoord.zw));
shadow *= 0.25;
return shadow;
}
float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
#ifdef GL_ARB_gpu_shader5
vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
vec4 gather = SHADOWGATHER(tex, coord);
#else
vec4 gather = vec4(0.0);
gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
#endif
vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
vec2 mx = mix( gather.wx, gather.zy, f.x );
return mix( mx.x, mx.y, f.y );
}
float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
vec2 pixSize = pixSize2 * shadowBorderScale;
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
float bound = KERNEL * 0.5 - 0.5;
bound *= PCFEDGE;
for (float y = -bound; y <= bound; y += PCFEDGE){
for (float x = -bound; x <= bound; x += PCFEDGE){
vec4 coord = vec4(projCoord.xy + vec2(x,y) * pixSize, projCoord.zw);
shadow += SHADOWCOMPARE(tex, coord);
}
}
shadow = shadow / (KERNEL * KERNEL);
return shadow;
}
//12 tap poisson disk
const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0){
return 1.0;
}
vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
//this is divided by 12
return shadow * 0.08333333333;
}
#ifdef POINTLIGHT
float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
float shadow = 1.0;
vec3 vect = worldPos.xyz - lightPos;
vec3 absv= abs(vect);
float maxComp = max(absv.x,max(absv.y,absv.z));
if(maxComp == absv.y){
if(vect.y < 0.0){
shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w);
}else{
shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
}else{
shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
}else{
shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
}
}
return shadow;
}
#else
#ifdef PSSM
float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
float shadow = 1.0;
if(shadowPosition < splits.x){
shadow = GETSHADOW(shadowMap0, projCoord0 );
}else if( shadowPosition < splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(shadowMap1, projCoord1);
}else if( shadowPosition < splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(shadowMap2, projCoord2);
}else if( shadowPosition < splits.w){
shadowBorderScale = 0.125;
shadow = GETSHADOW(shadowMap3, projCoord3);
}
return shadow;
}
#else
float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){
float shadow = 1.0;
projCoord /= projCoord.w;
shadow = GETSHADOW(shadowMap,projCoord);
//a small falloff to make the shadow blend nicely into the not lighten
//we translate the texture coordinate value to a -1,1 range so the length
//of the texture coordinate vector is actually the radius of the lighten area on the ground
projCoord = projCoord * 2.0 - 1.0;
float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
}
#endif
#endif

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

@ -178,9 +178,23 @@ public class J3MLoader implements AssetLoader {
return matchList;
}
private boolean isTexturePathDeclaredTheTraditionalWay(final int numberOfValues, final int numberOfTextureOptions, final String texturePath) {
return (numberOfValues > 1 && (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "))) || numberOfTextureOptions == 0;
private boolean isTexturePathDeclaredTheTraditionalWay(final List<TextureOptionValue> optionValues, final String texturePath) {
final boolean startsWithOldStyle = texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip ");
if (!startsWithOldStyle) {
return false;
}
if (optionValues.size() == 1 && (optionValues.get(0).textureOption == TextureOption.Flip || optionValues.get(0).textureOption == TextureOption.Repeat)) {
return true;
} else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Flip && optionValues.get(1).textureOption == TextureOption.Repeat) {
return true;
} else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Repeat && optionValues.get(1).textureOption == TextureOption.Flip) {
return true;
}
return false;
}
private Texture parseTextureType(final VarType type, final String value) {
@ -196,7 +210,7 @@ public class J3MLoader implements AssetLoader {
String texturePath = value.trim();
// If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way.
if (isTexturePathDeclaredTheTraditionalWay(textureValues.size(), textureOptionValues.size(), texturePath)) {
if (isTexturePathDeclaredTheTraditionalWay(textureOptionValues, texturePath)) {
boolean flipY = false;
if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) {
@ -453,6 +467,8 @@ public class J3MLoader implements AssetLoader {
renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1]));
}else if (split[0].equals("AlphaFunc")){
renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1]));
}else if (split[0].equals("LineWidth")){
renderState.setLineWidth(Float.parseFloat(split[1]));
} else {
throw new MatParseException(null, split[0], statement);
}
@ -679,6 +695,7 @@ public class J3MLoader implements AssetLoader {
material = new Material(def);
material.setKey(key);
material.setName(split[0].trim());
// material.setAssetName(fileName);
}else if (split.length == 1){
if (extending){

@ -80,6 +80,7 @@ public class J3MLoaderTest {
final Texture textureMin = Mockito.mock(Texture.class);
final Texture textureMag = Mockito.mock(Texture.class);
final Texture textureCombined = Mockito.mock(Texture.class);
final Texture textureLooksLikeOldStyle = Mockito.mock(Texture.class);
final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters);
final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip);
@ -88,6 +89,7 @@ public class J3MLoaderTest {
setupMockForTexture("Min", "min.png", false, textureMin);
setupMockForTexture("Mag", "mag.png", false, textureMag);
setupMockForTexture("Combined", "combined.png", true, textureCombined);
setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, textureLooksLikeOldStyle);
j3MLoader.load(assetInfo);

@ -6,6 +6,7 @@ Material Test : matdef.j3md {
Min: MinTrilinear "min.png"
Mag: MagBilinear "mag.png"
RepeatAxis: WrapRepeat_T "repeat-axis.png"
Combined: MagNearest MinBilinearNoMipMaps Flip WrapRepeat "combined.png"
Combined: Flip MagNearest MinBilinearNoMipMaps WrapRepeat "combined.png"
LooksLikeOldStyle: Flip WrapRepeat "oldstyle.png"
}
}

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

Loading…
Cancel
Save