Compare commits

...

92 Commits

Author SHA1 Message Date
Kirill Vainer 74a1d8b219 Merge branch 'master' into experimental 9 years ago
Kirill Vainer 3bb01d6963 Merge remote-tracking branch 'origin/master' into experimental 9 years ago
Kirill Vainer 4fcd575c47 MPO: fix unit test 9 years ago
Kirill Vainer 60d9d1b4d8 unit test: more descriptive failure message 9 years ago
Kirill Vainer 4ab5a9f7dd SkeletonControl: fix syntax error 9 years ago
Kirill Vainer 452c307d59 fix misc unit test issues due to merge 9 years ago
Kirill Vainer a8fca2bcf6 Merge branch 'master' into experimental 9 years ago
Kirill Vainer 2fc4e5b607 jinput: fix native loading 9 years ago
Kirill Vainer f28d74a1f6 joystick: use jinput backend for lwjgl3 9 years ago
Kirill Vainer 87af1f30b0 MPO: clone MPOs instead of sharing references 9 years ago
Kirill Vainer e691de4459 fix null checks 9 years ago
Kirill Vainer 2dafd1e485 MPO: add null override list check 9 years ago
Kirill Vainer c586bacbb0 MPO: clear param for null textures 9 years ago
Kirill Vainer fde095458e MPO: add javadoc 9 years ago
Kirill Vainer e8fd22223a MPO: use List instead of ArrayList 9 years ago
Kirill Vainer 53d3b72478 MPO: add ability to disable an override 9 years ago
Kirill Vainer 644b8167b8 MPO: add example 9 years ago
Kirill Vainer 2cdb4a8486 spatial: fix bug in remove/clear MPO 9 years ago
Kirill Vainer 152a7638cd material: fix sort id unit test failure 9 years ago
Kirill Vainer adc5084f5d MPO: implement overrides on uniforms and add test 9 years ago
Kirill Vainer 42d76cfd29 MPO: implement propagation and add test 9 years ago
Kirill Vainer ba22487c38 material: move technique logic into its own package 9 years ago
Kirill Vainer 00b6d904af add StaticPassLightingLogic 9 years ago
Kirill Vainer 2d2c394b42 Merge remote-tracking branch 'origin/master' into experimental 9 years ago
Kirill Vainer 655457ab6a Merge branch 'master' into experimental 9 years ago
Kirill Vainer 7639657fc1 initial implementation of MPO (untested!) 9 years ago
Kirill Vainer e87f008244 build: fix missing version tag 9 years ago
Kirill Vainer 12b3c4a140 build: fix build error 9 years ago
Kirill Vainer ae7fb6984c lwjgl3: re-enable native loading for native bullet 9 years ago
Kirill Vainer 3b9e412f80 build: fix build errors 9 years ago
Kirill Vainer 0aaa28e66b Merge branch 'master' into experimental 9 years ago
Kirill Vainer 9b45189f48 lwjgl3: start jME3 on main thread (needed for mac) 9 years ago
Kirill Vainer 69eaf39da9 lwjgl3: fix syntax error 9 years ago
Kirill Vainer 8024babb47 lwjgl3: use lwjgl's native loader 9 years ago
Kirill Vainer c7da1c4efd desktop: don't show dialogs when headless 9 years ago
Kirill Vainer ab8527770c GLRenderer: support VBO without VAO 9 years ago
Kirill Vainer c6b568c125 test: disable NativeLibraryLoaderIT 9 years ago
Kirill Vainer 772330c308 test: delete ApplicationTest 9 years ago
Kirill Vainer 8413ed715c point particles: various fixes 9 years ago
Kirill Vainer d76cb99772 point particles: improve performance by x3 9 years ago
Kirill Vainer 454e210d3d native library: don't run unit test 9 years ago
Kirill Vainer c6336c0781 version: new versioning scheme for tags 9 years ago
Kirill Vainer fda40563c5 build: don't build SDK or javadoc 9 years ago
Kirill Vainer 34aa21bfd9 Get rid of TestAwtPanels (it sucks) 9 years ago
Kirill Vainer f9969008c3 syntax error fixes 9 years ago
Kirill Vainer 2893ac9156 DDSLoader: fix syntax error 9 years ago
Kirill Vainer a3638f3e0c Merge branch 'master' into experimental 9 years ago
Kirill Vainer 27041e1341 LWJGL3 improvements 9 years ago
Kirill Vainer 962ab22ef4 Merge branch 'fbx-import-animation' into experimental 9 years ago
Kirill Vainer f9500f955f Merge branch 'renderer-rgtc' into experimental 9 years ago
Kirill Vainer c50839796f Merge branch 'renderer-fbreadasync' into experimental 9 years ago
Kirill Vainer ff6b1be725 Merge branch 'renderer-improvements' into experimental 9 years ago
Kirill Vainer 97281de5c4 Merge branch 'new_material_system' into experimental 9 years ago
Kirill Vainer 8f54af3263 Merge remote-tracking branch 'origin/master' into experimental 9 years ago
Kirill Vainer 79125f2f63 remove useless TestNativeLoader 9 years ago
Kirill Vainer 908b37350d remove XXX HACK from native library loader 9 years ago
Kirill Vainer f986043745 minor formatting changes 9 years ago
Kirill Vainer 961bf92734 lwjgl test: fix build exceed timeout 9 years ago
Kirill Vainer 3d82f5c459 lwjgl: add unit test 9 years ago
Kirill Vainer 4a646de49d gitignore: more cleanup 9 years ago
Kirill Vainer 06e8210e5d gitignore: cleanup 9 years ago
Kirill Vainer 85feb305ef SDK: fix build error 9 years ago
Kirill Vainer 15465a020f Merge branch 'master' into expermiental 9 years ago
Kirill Vainer f005c05f8d OffscreenBuffer: check needClose after runLoop 9 years ago
Kirill Vainer 42729b2302 FastMathTest: ignore failing test (for now) 9 years ago
Kirill Vainer 30855f5bb4 TestShaderNodes: fix build error 9 years ago
Kirill Vainer 352c02db8a DefineList: fix build error 9 years ago
Kirill Vainer ea4d750d52 RM: per-pass render method 9 years ago
Kirill Vainer 6db1d15045 Image: support for RGTC format 9 years ago
Kirill Vainer f9ce9e246c FBX: add ear clipping triangulator 9 years ago
Kirill Vainer 18db26292f Add the new material system 9 years ago
Kirill Vainer 0d3ebf75bd GLRenderer: fix NPE when using mesh without index buffer 10 years ago
Kirill Vainer 4e572605a8 GLRenderer: merge changes from master 10 years ago
Kirill Vainer 28e2b5650c GLRenderer: disable global VAO, since VAO is now supported 10 years ago
Kirill Vainer 3d2a9b83e9 JOGL: fix syntax error due to missing renderer 10 years ago
Kirill Vainer 618c8d02eb JOGL: delete old / broken renderer 10 years ago
Kirill Vainer 3a00aff886 GLRenderer: clear VBO bind state after bounding VAO 10 years ago
Kirill Vainer fc680ea121 GLRenderer: use luminance instead of intensity in compare R to texture 10 years ago
Kirill Vainer 12c001addc Uniform: fix crash when using vector4array 10 years ago
Kirill Vainer 7b147171bf VAO: changes to test 10 years ago
Kirill Vainer 2e9a9f9f9e Add test for VAO 10 years ago
Kirill Vainer c72b036c9f VirtualIndexBuffer: fix compile error 10 years ago
Kirill Vainer 81a76fdf69 Mesh: preliminary work to use ubyte weights for hardware skinning 10 years ago
Kirill Vainer 7b64e91681 GLRenderer: remaining portion of VAO support 10 years ago
Kirill Vainer d2f38f8adb GLRenderer: added fast uniforms - still need to fix Uniform.clear() 10 years ago
Kirill Vainer 937d97b8d7 GLRenderer: initial VAO support (still buggy) 10 years ago
Kirill Vainer 9e17f39cfb FBX: include main folder for shared plugins API 10 years ago
Kirill Vainer 2ced7653a7 FBX: more work on importing skeletal animation 10 years ago
Kirill Vainer f0b63e7910 GLRenderer: the actual async FB read changes 10 years ago
Kirill Vainer 860de88298 GLRenderer: initial commit of async FB read (including jme panels) 10 years ago
Kirill Vainer e8f344a0db GLRenderer: remaining portion of VAO support 10 years ago
Kirill Vainer bee759bddc GLRenderer: initial VAO support (still buggy) 10 years ago
  1. 82
      gradle.properties
  2. 17
      jme3-core/build.gradle
  3. 5
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  4. 3
      jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java
  5. 186
      jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java
  6. 3
      jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java
  7. 5
      jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java
  8. 7
      jme3-core/src/main/java/com/jme3/renderer/Caps.java
  9. 6
      jme3-core/src/main/java/com/jme3/renderer/Renderer.java
  10. 280
      jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java
  11. 98
      jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java
  12. 2
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
  13. 5
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  14. 456
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  15. 267
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  16. 25
      jme3-core/src/main/java/com/jme3/scene/Node.java
  17. 30
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  18. 17
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  19. 2
      jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java
  20. 10
      jme3-core/src/main/java/com/jme3/shader/Shader.java
  21. 28
      jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java
  22. 3
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  23. 16
      jme3-core/src/main/java/com/jme3/texture/Image.java
  24. 3
      jme3-core/src/main/java/com/jme3/util/NativeObject.java
  25. 4
      jme3-core/src/main/resources/com/jme3/asset/General.cfg
  26. 80
      jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java
  27. 8
      jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java
  28. 35
      jme3-core/src/test/java/com/jme3/IntegrationTest.java
  29. 60
      jme3-core/src/test/java/com/jme3/app/AppSettingsIT.java
  30. 1
      jme3-desktop/build.gradle
  31. 359
      jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java
  32. 270
      jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java
  33. 32
      jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java
  34. 48
      jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java
  35. 382
      jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java
  36. 203
      jme3-desktop/src/test/java/com/jme3/system/NativeLibraryLoaderIT.java
  37. 88
      jme3-examples/src/main/java/jme3test/app/TestCustomAppSettings.java
  38. 112
      jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java
  39. 141
      jme3-examples/src/main/java/jme3test/stress/TestUniqueGeometries.java
  40. 12
      jme3-jinput/build.gradle
  41. 56
      jme3-jinput/src/main/java/com/jme3/input/jinput/JInputJoyInput.java
  42. 529
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java
  43. 3
      jme3-lwjgl/build.gradle
  44. 2
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java
  45. 14
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  46. 6
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java
  47. 1
      jme3-lwjgl3/build.gradle
  48. 3
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  49. 3
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java
  50. 1
      jme3-plugins/build.gradle
  51. 56
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java
  52. 10
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java
  53. 16
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java
  54. 26
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java
  55. 54
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java
  56. 112
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkeleton.java
  57. 78
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java
  58. 22
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java
  59. 25
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java
  60. 6
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java
  61. 70
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java
  62. 60
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java
  63. 35
      jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java
  64. 241
      jme3-plugins/src/main/java/com/jme3/scene/plugins/triangulator/EarClippingTriangulator.java
  65. 64
      jme3-plugins/src/test/java/com/jme3/scene/plugins/triangulator/TriangulatorTest.java
  66. 1
      settings.gradle
  67. 29
      version.gradle

@ -1,42 +1,40 @@
# Version number used for plugins, only 3 numbers (e.g. 3.1.3) # Version number used for plugins, only 3 numbers (e.g. 3.1.3)
jmeVersion = 3.1.0 jmeVersion = 3.1.0
# Version used for application and settings folder, no spaces! # Version used for application and settings folder, no spaces!
jmeMainVersion = 3.1 jmeMainVersion = 3.1
# Version addition pre-alpha-svn, Stable, Beta # Increment this each time jmeVersionTag changes but jmeVersion stays the same
jmeVersionTag = SNAPSHOT jmeVersionTagID = 0
# Increment this each time jmeVersionTag changes but jmeVersion stays the same
jmeVersionTagID = 0 # specify if JavaDoc should be built
buildJavaDoc = false
# specify if JavaDoc should be built
buildJavaDoc = true # specify if SDK and Native libraries get built
buildNativeProjects = false
# specify if SDK and Native libraries get built buildAndroidExamples = false
buildNativeProjects = false
buildAndroidExamples = false # Path to android NDK for building native libraries
#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7
# Path to android NDK for building native libraries ndkPath = /opt/android-ndk-r10c
#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7
ndkPath = /opt/android-ndk-r10c # Path for downloading native Bullet
bulletUrl = http://bullet.googlecode.com/files/bullet-2.82-r2704.zip
# Path for downloading native Bullet bulletFolder = bullet-2.82-r2704
bulletUrl = http://bullet.googlecode.com/files/bullet-2.82-r2704.zip bulletZipFile = bullet.zip
bulletFolder = bullet-2.82-r2704
bulletZipFile = bullet.zip # Path for downloading NetBeans Base
netbeansUrl = http://download.netbeans.org/netbeans/8.0.2/final/zip/netbeans-8.0.2-201411181905-javase.zip
# Path for downloading NetBeans Base
netbeansUrl = http://download.netbeans.org/netbeans/8.0.2/final/zip/netbeans-8.0.2-201411181905-javase.zip # POM settings
POM_NAME=jMonkeyEngine
# POM settings POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers
POM_NAME=jMonkeyEngine POM_URL=http://jmonkeyengine.org
POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine
POM_URL=http://jmonkeyengine.org POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git
POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git
POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git POM_LICENSE_NAME=New BSD (3-clause) License
POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause
POM_LICENSE_NAME=New BSD (3-clause) License POM_LICENSE_DISTRIBUTION=repo
POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause
POM_LICENSE_DISTRIBUTION=repo # Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline
bintray_user=
# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline bintray_api_key=
bintray_user=
bintray_api_key=

@ -13,10 +13,24 @@ sourceSets {
test { test {
java { java {
srcDir 'src/test/java' srcDir 'src/test/java'
srcDir 'src/plugins/java'
} }
} }
} }
configurations {
testOutput.extendsFrom testCompile
}
task testJar(type: Jar) {
classifier "test"
from sourceSets.test.output
}
artifacts {
testOutput testJar
}
task updateVersionPropertiesFile << { task updateVersionPropertiesFile << {
def verfile = file('src/main/resources/com/jme3/system/version.properties') def verfile = file('src/main/resources/com/jme3/system/version.properties')
verfile.text = "# THIS IS AN AUTO-GENERATED FILE..\n" + verfile.text = "# THIS IS AN AUTO-GENERATED FILE..\n" +
@ -34,6 +48,3 @@ task updateVersionPropertiesFile << {
} }
compileJava.dependsOn(updateVersionPropertiesFile) compileJava.dependsOn(updateVersionPropertiesFile)
dependencies {
}

@ -1165,7 +1165,8 @@ public class ParticleEmitter extends Geometry {
inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal(); inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
} }
particleMesh.updateParticleData(particles, cam, inverseRotation); particleMesh.updateParticleData(rm, particles, cam, inverseRotation);
if (!worldSpace) { if (!worldSpace) {
vars.release(); vars.release();
} }
@ -1173,7 +1174,7 @@ public class ParticleEmitter extends Geometry {
public void preload(RenderManager rm, ViewPort vp) { public void preload(RenderManager rm, ViewPort vp) {
this.updateParticleState(0); this.updateParticleState(0);
particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY); particleMesh.updateParticleData(rm, particles, vp.getCamera(), Matrix3f.IDENTITY);
} }
@Override @Override

@ -34,6 +34,7 @@ package com.jme3.effect;
import com.jme3.material.RenderState; import com.jme3.material.RenderState;
import com.jme3.math.Matrix3f; import com.jme3.math.Matrix3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
/** /**
@ -80,6 +81,6 @@ public abstract class ParticleMesh extends Mesh {
/** /**
* Update the particle visual data. Typically called every frame. * Update the particle visual data. Typically called every frame.
*/ */
public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation); public abstract void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation);
} }

@ -33,15 +33,24 @@ package com.jme3.effect;
import com.jme3.math.Matrix3f; import com.jme3.math.Matrix3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
public class ParticlePointMesh extends ParticleMesh { public class ParticlePointMesh extends ParticleMesh {
private static final int POS_SIZE = 3 * 4;
private static final int COLOR_SIZE = 4 * 1;
private static final int SIZE_SIZE = 1 * 4;
private static final int UV_SIZE = 4 * 4;
private static final int BYTES_PER_PARTICLE = POS_SIZE + COLOR_SIZE + SIZE_SIZE + UV_SIZE;
private static final int FLOATS_PER_PARTICLE = BYTES_PER_PARTICLE / 4;
private ParticleEmitter emitter; private ParticleEmitter emitter;
private int imagesX = 1; private int imagesX = 1;
@ -59,109 +68,108 @@ public class ParticlePointMesh extends ParticleMesh {
this.emitter = emitter; this.emitter = emitter;
// set positions ByteBuffer eb = BufferUtils.createByteBuffer(BYTES_PER_PARTICLE * numParticles);
FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles); VertexBuffer vb = getBuffer(VertexBuffer.Type.InterleavedData);
if (vb != null) {
//if the buffer is already set only update the data vb.updateData(eb);
VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
if (buf != null) {
buf.updateData(pb);
} else { } else {
VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position); vb = new VertexBuffer(VertexBuffer.Type.InterleavedData);
pvb.setupData(Usage.Stream, 3, Format.Float, pb); vb.setupData(Usage.Stream, 1, Format.Byte, eb);
setBuffer(pvb); setBuffer(vb);
} }
// set colors if (getBuffer(VertexBuffer.Type.Position) == null) {
ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4); VertexBuffer pb = new VertexBuffer(VertexBuffer.Type.Position);
pb.setupData(Usage.Stream, 3, Format.Float, eb);
buf = getBuffer(VertexBuffer.Type.Color); pb.updateData(null);
if (buf != null) { pb.setOffset(0);
buf.updateData(cb); pb.setStride(BYTES_PER_PARTICLE);
} else { setBuffer(pb);
VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb); VertexBuffer cb = new VertexBuffer(VertexBuffer.Type.Color);
cvb.setNormalized(true); cb.setupData(Usage.Stream, 4, Format.UnsignedByte, eb);
setBuffer(cvb); cb.updateData(null);
} cb.setNormalized(true);
cb.setOffset(POS_SIZE);
// set sizes cb.setStride(BYTES_PER_PARTICLE);
FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles); setBuffer(cb);
buf = getBuffer(VertexBuffer.Type.Size); VertexBuffer sb = new VertexBuffer(VertexBuffer.Type.Size);
if (buf != null) { sb.setupData(Usage.Stream, 1, Format.Float, eb);
buf.updateData(sb); sb.updateData(null);
} else { sb.setOffset(POS_SIZE + COLOR_SIZE);
VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size); sb.setStride(BYTES_PER_PARTICLE);
svb.setupData(Usage.Stream, 1, Format.Float, sb); setBuffer(sb);
setBuffer(svb);
VertexBuffer tb = new VertexBuffer(VertexBuffer.Type.TexCoord);
tb.setupData(Usage.Stream, 4, Format.Float, eb);
tb.updateData(null);
tb.setOffset(POS_SIZE + COLOR_SIZE + SIZE_SIZE);
tb.setStride(BYTES_PER_PARTICLE);
setBuffer(tb);
} }
// set UV-scale
FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4);
buf = getBuffer(VertexBuffer.Type.TexCoord);
if (buf != null) {
buf.updateData(tb);
} else {
VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
tvb.setupData(Usage.Stream, 4, Format.Float, tb);
setBuffer(tvb);
}
updateCounts(); updateCounts();
} }
@Override @Override
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) { public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position); VertexBuffer eb = getBuffer(VertexBuffer.Type.InterleavedData);
FloatBuffer positions = (FloatBuffer) pvb.getData(); ByteBuffer elements = (ByteBuffer) eb.getData();
FloatBuffer floatElements = elements.asFloatBuffer();
VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color); float sizeScale = emitter.getWorldScale().x;
ByteBuffer colors = (ByteBuffer) cvb.getData();
VertexBuffer svb = getBuffer(VertexBuffer.Type.Size); TempVars vars = TempVars.get();
FloatBuffer sizes = (FloatBuffer) svb.getData(); try {
float[] floatArray = vars.skinTangents;
VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord); int particlesPerIteration = floatArray.length / FLOATS_PER_PARTICLE;
FloatBuffer texcoords = (FloatBuffer) tvb.getData(); int iterations = (particles.length + particlesPerIteration - 1) / particlesPerIteration;
float sizeScale = emitter.getWorldScale().x; int particleIndex = 0;
for (int iteration = 0; iteration < iterations; iteration++) {
int particlesRemaining = Math.min(
particles.length - particleIndex,
particlesPerIteration);
int floatIndex = 0;
for (int i = 0; i < particlesRemaining; i++) {
Particle p = particles[particleIndex++];
floatArray[floatIndex++] = p.position.x;
floatArray[floatIndex++] = p.position.y;
floatArray[floatIndex++] = p.position.z;
floatArray[floatIndex++] = Float.intBitsToFloat(p.color.asIntABGR());
floatArray[floatIndex++] = p.size * sizeScale;
int imgX = p.imageIndex % imagesX;
int imgY = (p.imageIndex - imgX) / imagesY;
float startX = ((float) imgX) / imagesX;
float startY = ((float) imgY) / imagesY;
float endX = startX + (1f / imagesX);
float endY = startY + (1f / imagesY);
floatArray[floatIndex++] = startX;
floatArray[floatIndex++] = startY;
floatArray[floatIndex++] = endX;
floatArray[floatIndex++] = endY;
}
floatElements.put(floatArray, 0, FLOATS_PER_PARTICLE * particlesRemaining);
}
if (floatElements.remaining() != 0) {
throw new IllegalStateException();
}
eb.updateData(elements);
// update data in vertex buffers // cheating!
positions.rewind(); rm.getRenderer().updateBufferData(eb);
colors.rewind(); } finally {
sizes.rewind(); vars.release();
texcoords.rewind();
for (int i = 0; i < particles.length; i++){
Particle p = particles[i];
positions.put(p.position.x)
.put(p.position.y)
.put(p.position.z);
sizes.put(p.size * sizeScale);
colors.putInt(p.color.asIntABGR());
int imgX = p.imageIndex % imagesX;
int imgY = (p.imageIndex - imgX) / imagesY;
float startX = ((float) imgX) / imagesX;
float startY = ((float) imgY) / imagesY;
float endX = startX + (1f / imagesX);
float endY = startY + (1f / imagesY);
texcoords.put(startX).put(startY).put(endX).put(endY);
} }
positions.flip();
colors.flip();
sizes.flip();
texcoords.flip();
// force renderer to re-send data to GPU
pvb.updateData(positions);
cvb.updateData(colors);
svb.updateData(sizes);
tvb.updateData(texcoords);
} }
} }

@ -35,6 +35,7 @@ import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f; import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.VertexBuffer.Usage;
@ -145,7 +146,7 @@ public class ParticleTriMesh extends ParticleMesh {
} }
@Override @Override
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) { public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
// System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length); // System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length);
// comparator.setCamera(cam); // comparator.setCamera(cam);
// Arrays.sort(particlesCopy, comparator); // Arrays.sort(particlesCopy, comparator);

@ -145,6 +145,11 @@ class BitmapTextPage extends Geometry {
Mesh m = getMesh(); Mesh m = getMesh();
int vertCount = pageQuads.size() * 4; int vertCount = pageQuads.size() * 4;
int triCount = pageQuads.size() * 2; int triCount = pageQuads.size() * 2;
if (vertCount > m.getVertexCount() ||
triCount > m.getTriangleCount()) {
m.setUpdateNeeded();
}
VertexBuffer pb = m.getBuffer(Type.Position); VertexBuffer pb = m.getBuffer(Type.Position);
VertexBuffer tb = m.getBuffer(Type.TexCoord); VertexBuffer tb = m.getBuffer(Type.TexCoord);

@ -349,7 +349,12 @@ public enum Caps {
/** /**
* GPU can provide and accept binary shaders. * GPU can provide and accept binary shaders.
*/ */
BinaryShader; BinaryShader,
/**
* Supports {@link Format#RGTC} and {@link Format#RTC} texture compression.
*/
TextureCompressionRGTC;
/** /**
* Returns true if given the renderer capabilities, the texture * Returns true if given the renderer capabilities, the texture

@ -283,6 +283,12 @@ public interface Renderer {
* the per-instance attributes. * the per-instance attributes.
*/ */
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData); public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData);
/**
* Delete a Mesh (or Vertex Array Object in GL terms) from the GPU.
* @param mesh The mesh to delete.
*/
public void deleteMesh(Mesh mesh);
/** /**
* Resets all previously used {@link NativeObject Native Objects} on this Renderer. * Resets all previously used {@link NativeObject Native Objects} on this Renderer.

@ -0,0 +1,280 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.renderer.opengl;
import com.jme3.renderer.RenderContext;
import com.jme3.renderer.RendererException;
import com.jme3.texture.FrameBuffer;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author Kirill Vainer
*/
final class AsyncFrameReader {
private final ArrayList<PixelBuffer> pboPool = new ArrayList<PixelBuffer>();
private final List<FrameBufferReadRequest> pending = Collections.synchronizedList(new ArrayList<FrameBufferReadRequest>());
private final GLRenderer renderer;
private final GL gl;
private final GLExt glext;
private final IntBuffer intBuf = BufferUtils.createIntBuffer(1);
private final RenderContext context;
private final Thread glThread;
AsyncFrameReader(GLRenderer renderer, GL gl, GLExt glext, RenderContext context) {
this.renderer = renderer;
this.gl = gl;
this.glext = glext;
this.context = context;
this.glThread = Thread.currentThread();
}
private PixelBuffer acquirePixelBuffer(int dataSize) {
PixelBuffer pb;
if (pboPool.isEmpty()) {
// create PBO
pb = new PixelBuffer();
intBuf.clear();
gl.glGenBuffers(intBuf);
pb.id = intBuf.get(0);
} else {
// reuse PBO.
pb = pboPool.remove(pboPool.size() - 1);
}
// resize or allocate PBO if required.
if (pb.size != dataSize) {
if (context.boundPixelPackPBO != pb.id) {
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pb.id);
context.boundPixelPackPBO = pb.id;
}
gl.glBufferData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, dataSize, GL.GL_STREAM_READ);
}
pb.size = dataSize;
return pb;
}
private void readFrameBufferFromPBO(FrameBufferReadRequest fbrr) {
// assumes waitForCompletion was already called!
if (context.boundPixelPackPBO != fbrr.pb.id) {
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, fbrr.pb.id);
context.boundPixelPackPBO = fbrr.pb.id;
}
gl.glGetBufferSubData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0, fbrr.targetBuf);
}
private boolean waitForCompletion(FrameBufferReadRequest fbrr, long time, TimeUnit unit, boolean flush) {
int flags = flush ? GLExt.GL_SYNC_FLUSH_COMMANDS_BIT : 0;
long nanos = unit.toNanos(time);
switch (glext.glClientWaitSync(fbrr.fence, flags, nanos)) {
case GLExt.GL_ALREADY_SIGNALED:
case GLExt.GL_CONDITION_SATISFIED:
return true;
case GLExt.GL_TIMEOUT_EXPIRED:
return false;
case GLExt.GL_WAIT_FAILED:
throw new RendererException("Waiting for fence failed");
default:
throw new RendererException("Unexpected result from glClientWaitSync");
}
}
private void signalFinished(FrameBufferReadRequest fbrr) {
fbrr.lock.lock();
try {
fbrr.done = true;
fbrr.cond.signalAll();
} finally {
fbrr.lock.unlock();
}
}
void signalCancelled(FrameBufferReadRequest fbrr) {
fbrr.lock.lock();
try {
fbrr.cancelled = true;
fbrr.cond.signalAll();
} finally {
fbrr.lock.unlock();
}
}
public void updateReadRequests() {
// Update requests in the order they were made (e.g. earliest first)
for (Iterator<FrameBufferReadRequest> it = pending.iterator(); it.hasNext();) {
FrameBufferReadRequest fbrr = it.next();
// Check status for the user... (non-blocking)
if (!fbrr.cancelled && !fbrr.done) {
// Request a flush if we know clients are waiting
// (to speed up the process, or make it take finite time ..)
boolean flush = false; // fbrr.clientsWaiting.get() > 0;
if (waitForCompletion(fbrr, 0, TimeUnit.NANOSECONDS, flush)) {
if (!fbrr.cancelled) {
// Operation completed.
// Read data into user's ByteBuffer
readFrameBufferFromPBO(fbrr);
// Signal any waiting threads that we are done.
// Also, set the done flag.
signalFinished(fbrr);
}
}
}
if (fbrr.cancelled || fbrr.done) {
// Cleanup
// Return the pixel buffer back into the pool.
if (!pboPool.contains(fbrr.pb)) {
pboPool.add(fbrr.pb);
}
// Remove this request from the pending requests list.
it.remove();
// Get rid of the fence
glext.glDeleteSync(fbrr.fence);
fbrr.pb = null;
fbrr.fence = null;
}
}
}
ByteBuffer getFrameBufferData(FrameBufferReadRequest fbrr, long time, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (fbrr.cancelled) {
throw new CancellationException();
}
if (fbrr.done) {
return fbrr.targetBuf;
}
if (glThread == Thread.currentThread()) {
// Running on GL thread, hence can use GL commands ..
try {
// Wait until we reach the fence..
// PROBLEM: if the user is holding any locks,
// they will not be released here,
// causing a potential deadlock!
if (!waitForCompletion(fbrr, time, unit, true)) {
throw new TimeoutException();
}
// Command stream reached this point.
if (fbrr.cancelled) {
// User not interested in this anymore.
throw new CancellationException();
} else {
// Read data into user's ByteBuffer
readFrameBufferFromPBO(fbrr);
}
// Mark it as done, so future get() calls always return.
signalFinished(fbrr);
return fbrr.targetBuf;
} catch (RendererException ex) {
throw new ExecutionException(ex);
}
} else {
long nanos = unit.toNanos(time);
fbrr.lock.lock();
try {
// Not running on GL thread, indicate that we are running
// so GL thread can request GPU to finish quicker ...
fbrr.clientsWaiting.getAndIncrement();
// Wait until we finish
while (!fbrr.done && !fbrr.cancelled) {
if (nanos <= 0L) {
throw new TimeoutException();
}
nanos = fbrr.cond.awaitNanos(nanos);
}
if (fbrr.cancelled) {
throw new CancellationException();
}
return fbrr.targetBuf;
} finally {
fbrr.lock.unlock();
fbrr.clientsWaiting.getAndDecrement();
}
}
}
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
// Create & allocate a PBO (or reuse an existing one if available)
FrameBufferReadRequest fbrr = new FrameBufferReadRequest(this);
fbrr.targetBuf = byteBuf;
int desiredSize = fb.getWidth() * fb.getHeight() * 4;
if (byteBuf.remaining() != desiredSize) {
throw new IllegalArgumentException("Ensure buffer size matches framebuffer size");
}
fbrr.pb = acquirePixelBuffer(desiredSize);
// Read into PBO (asynchronous)
// renderer.readFrameBufferWithGLFormat(fb, null, GL2.GL_BGRA, GL2.GL_UNSIGNED_BYTE, fbrr.pb.id);
// Insert fence into command stream.
fbrr.fence = glext.glFenceSync(GLExt.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// Insert into FIFO
pending.add(fbrr);
return fbrr;
}
}

@ -0,0 +1,98 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.renderer.opengl;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class PixelBuffer {
int id = -1;
int size = -1;
}
class FrameBufferReadRequest implements Future<ByteBuffer> {
AsyncFrameReader reader;
Object fence;
PixelBuffer pb;
ByteBuffer targetBuf;
boolean cancelled;
boolean done;
final ReentrantLock lock = new ReentrantLock();
final Condition cond = lock.newCondition();
final AtomicInteger clientsWaiting = new AtomicInteger(0);
public FrameBufferReadRequest(AsyncFrameReader reader) {
this.reader = reader;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (isDone()) {
return false;
}
reader.signalCancelled(this);
return true;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean isDone() {
return done;
}
@Override
public ByteBuffer get(long l, TimeUnit tu) throws InterruptedException, ExecutionException, TimeoutException {
return reader.getFrameBufferData(this, l, tu);
}
@Override
public ByteBuffer get() throws InterruptedException, ExecutionException {
try {
return get(1, TimeUnit.SECONDS);
} catch (TimeoutException ex) {
throw new ExecutionException(ex);
}
}
}

@ -44,6 +44,8 @@ import java.nio.IntBuffer;
public interface GLExt { public interface GLExt {
public static final int GL_ALREADY_SIGNALED = 0x911A; public static final int GL_ALREADY_SIGNALED = 0x911A;
public static final int GL_COMPRESSED_RED_RGTC1 = 0x8DBB;
public static final int GL_COMPRESSED_RG_RGTC2 = 0x8DBD;
public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274; public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274;
public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;

@ -233,6 +233,11 @@ public final class GLImageFormats {
formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
} }
if (caps.contains(Caps.TextureCompressionRGTC)) {
formatComp(formatToGL, Format.RGTC, GLExt.GL_COMPRESSED_RG_RGTC2, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
formatComp(formatToGL, Format.RTC, GLExt.GL_COMPRESSED_RED_RGTC1, GL.GL_RED, GL.GL_UNSIGNED_BYTE);
}
return formatToGL; return formatToGL;
} }
} }

@ -64,6 +64,7 @@ import java.util.EnumMap;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -100,6 +101,7 @@ public final class GLRenderer implements Renderer {
private final GLExt glext; private final GLExt glext;
private final GLFbo glfbo; private final GLFbo glfbo;
private final TextureUtil texUtil; private final TextureUtil texUtil;
private final AsyncFrameReader frameReader;
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) { public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl; this.gl = gl;
@ -109,6 +111,7 @@ public final class GLRenderer implements Renderer {
this.glfbo = glfbo; this.glfbo = glfbo;
this.glext = glext; this.glext = glext;
this.texUtil = new TextureUtil(gl, gl2, glext); this.texUtil = new TextureUtil(gl, gl2, glext);
this.frameReader = new AsyncFrameReader(this, gl, glext, context);
} }
@Override @Override
@ -348,6 +351,10 @@ public final class GLRenderer implements Renderer {
} else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) { } else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) {
caps.add(Caps.TextureCompressionETC1); caps.add(Caps.TextureCompressionETC1);
} }
if (hasExtension("GL_ARB_texture_compression_rgtc")) {
caps.add(Caps.TextureCompressionRGTC);
}
// == end texture format extensions == // == end texture format extensions ==
@ -379,7 +386,7 @@ public final class GLRenderer implements Renderer {
limits.put(Limits.TextureAnisotropy, getInteger(GLExt.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)); limits.put(Limits.TextureAnisotropy, getInteger(GLExt.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT));
} }
if (hasExtension("GL_EXT_framebuffer_object") if (hasExtension("GL_EXT_framebuffer_object")
|| caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGL30)
|| caps.contains(Caps.OpenGLES20)) { || caps.contains(Caps.OpenGLES20)) {
caps.add(Caps.FrameBuffer); caps.add(Caps.FrameBuffer);
@ -474,7 +481,6 @@ public final class GLRenderer implements Renderer {
{ {
sb.append("\t").append(cap.toString()).append("\n"); sb.append("\t").append(cap.toString()).append("\n");
} }
sb.append("\nHardware limits: \n"); sb.append("\nHardware limits: \n");
for (Limits limit : Limits.values()) { for (Limits limit : Limits.values()) {
Integer value = limits.get(limit); Integer value = limits.get(limit);
@ -484,7 +490,7 @@ public final class GLRenderer implements Renderer {
sb.append("\t").append(limit.name()).append(" = ") sb.append("\t").append(limit.name()).append(" = ")
.append(value).append("\n"); .append(value).append("\n");
} }
logger.log(Level.FINE, sb.toString()); logger.log(Level.FINE, sb.toString());
} }
@ -525,9 +531,9 @@ public final class GLRenderer implements Renderer {
if (caps.contains(Caps.CoreProfile)) { if (caps.contains(Caps.CoreProfile)) {
// Core Profile requires VAO to be bound. // Core Profile requires VAO to be bound.
gl3.glGenVertexArrays(intBuf16); // gl3.glGenVertexArrays(intBuf16);
int vaoId = intBuf16.get(0); // int vaoId = intBuf16.get(0);
gl3.glBindVertexArray(vaoId); // gl3.glBindVertexArray(vaoId);
} }
if (gl2 != null) { if (gl2 != null) {
gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE); gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
@ -883,6 +889,7 @@ public final class GLRenderer implements Renderer {
public void postFrame() { public void postFrame() {
objManager.deleteUnused(this); objManager.deleteUnused(this);
frameReader.updateReadRequests();
gl.resetStats(); gl.resetStats();
} }
@ -1524,15 +1531,15 @@ public final class GLRenderer implements Renderer {
bindFrameBuffer(fb); bindFrameBuffer(fb);
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
updateFrameBufferAttachment(fb, colorBuf);
}
FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
if (depthBuf != null) { if (depthBuf != null) {
updateFrameBufferAttachment(fb, depthBuf); updateFrameBufferAttachment(fb, depthBuf);
} }
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
updateFrameBufferAttachment(fb, colorBuf);
}
setReadDrawBuffers(fb); setReadDrawBuffers(fb);
checkFrameBufferError(); checkFrameBufferError();
@ -1697,11 +1704,11 @@ public final class GLRenderer implements Renderer {
} }
} }
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); return frameReader.readFrameBufferLater(fb, byteBuf);
} }
private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) { void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType, int pboId) {
if (fb != null) { if (fb != null) {
RenderBuffer rb = fb.getColorBuffer(); RenderBuffer rb = fb.getColorBuffer();
if (rb == null) { if (rb == null) {
@ -1720,12 +1727,30 @@ public final class GLRenderer implements Renderer {
setFrameBuffer(null); setFrameBuffer(null);
} }
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf); if (context.boundPixelPackPBO != pboId) {
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pboId);
context.boundPixelPackPBO = pboId;
}
if (byteBuf == null) {
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, 0);
} else {
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
}
if (context.boundPixelPackPBO != 0) {
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0);
context.boundPixelPackPBO = 0;
}
} }
public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false); GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false);
readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType); readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType, 0);
}
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
readFrameBufferWithFormat(fb, byteBuf, Image.Format.RGBA8);
} }
private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) {
@ -2346,32 +2371,37 @@ public final class GLRenderer implements Renderer {
context.attribIndexList.copyNewToOld(); context.attribIndexList.copyNewToOld();
} }
public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { private int updateAttributeLocation(Shader shader, VertexBuffer.Type attribType) {
if (vb.getBufferType() == VertexBuffer.Type.Index) { Attribute attrib = shader.getAttribute(attribType);
throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
}
if (context.boundShaderProgram <= 0) {
throw new IllegalStateException("Cannot render mesh without shader bound");
}
Attribute attrib = context.boundShader.getAttribute(vb.getBufferType());
int loc = attrib.getLocation(); int loc = attrib.getLocation();
if (loc == -1) { if (loc == -1) {
return; // not defined return -1; // not defined
} }
if (loc == -2) { if (loc == -2) {
loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + vb.getBufferType().name()); loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + attribType.name());
// not really the name of it in the shader (inPosition) but // not really the name of it in the shader (inPosition) but
// the internal name of the enum (Position). // the internal name of the enum (Position).
if (loc < 0) { if (loc < 0) {
attrib.setLocation(-1); attrib.setLocation(-1);
return; // not available in shader. return -1; // not available in shader.
} else { } else {
attrib.setLocation(loc); attrib.setLocation(loc);
} }
} }
return loc;
}
public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
if (vb.getBufferType() == VertexBuffer.Type.Index) {
throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
}
Shader shader = context.boundShader;
int location = updateAttributeLocation(shader, vb.getBufferType());
if (location == -1) {
return;
}
if (vb.isInstanced()) { if (vb.isInstanced()) {
if (!caps.contains(Caps.MeshInstancing)) { if (!caps.contains(Caps.MeshInstancing)) {
@ -2395,11 +2425,11 @@ public final class GLRenderer implements Renderer {
VertexBuffer[] attribs = context.boundAttribs; VertexBuffer[] attribs = context.boundAttribs;
for (int i = 0; i < slotsRequired; i++) { for (int i = 0; i < slotsRequired; i++) {
if (!context.attribIndexList.moveToNew(loc + i)) { if (!context.attribIndexList.moveToNew(location + i)) {
gl.glEnableVertexAttribArray(loc + i); gl.glEnableVertexAttribArray(location + i);
} }
} }
if (attribs[loc] != vb) { if (attribs[location] != vb) {
// NOTE: Use id from interleaved buffer if specified // NOTE: Use id from interleaved buffer if specified
int bufId = idb != null ? idb.getId() : vb.getId(); int bufId = idb != null ? idb.getId() : vb.getId();
assert bufId != -1; assert bufId != -1;
@ -2412,12 +2442,12 @@ public final class GLRenderer implements Renderer {
} }
if (slotsRequired == 1) { if (slotsRequired == 1) {
gl.glVertexAttribPointer(loc, gl.glVertexAttribPointer(location,
vb.getNumComponents(), vb.getNumComponents(),
convertFormat(vb.getFormat()), convertFormat(vb.getFormat()),
vb.isNormalized(), vb.isNormalized(),
vb.getStride(), vb.getStride(),
vb.getOffset()); vb.getOffset());
} else { } else {
for (int i = 0; i < slotsRequired; i++) { for (int i = 0; i < slotsRequired; i++) {
// The pointer maps the next 4 floats in the slot. // The pointer maps the next 4 floats in the slot.
@ -2428,17 +2458,17 @@ public final class GLRenderer implements Renderer {
// P4: ____________XXXX____________XXXX // P4: ____________XXXX____________XXXX
// stride = 4 bytes in float * 4 floats in slot * num slots // stride = 4 bytes in float * 4 floats in slot * num slots
// offset = 4 bytes in float * 4 floats in slot * slot index // offset = 4 bytes in float * 4 floats in slot * slot index
gl.glVertexAttribPointer(loc + i, gl.glVertexAttribPointer(location + i,
4, 4,
convertFormat(vb.getFormat()), convertFormat(vb.getFormat()),
vb.isNormalized(), vb.isNormalized(),
4 * 4 * slotsRequired, 4 * 4 * slotsRequired,
4 * 4 * i); 4 * 4 * i);
} }
} }
for (int i = 0; i < slotsRequired; i++) { for (int i = 0; i < slotsRequired; i++) {
int slot = loc + i; int slot = location + i;
if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) { if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
// non-instanced -> instanced // non-instanced -> instanced
glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan()); glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
@ -2451,13 +2481,97 @@ public final class GLRenderer implements Renderer {
} }
} }
/**
* Set VBO on VAO. Assumes a brand new mesh or modified mesh with new buffer.
*
* @param vb
* @param idb
*/
public void setVertexAttribVAO(VertexBuffer vb, VertexBuffer idb) {
if (vb.getBufferType() == VertexBuffer.Type.Index) {
throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
}
Shader shader = context.boundShader;
int location = updateAttributeLocation(shader, vb.getBufferType());
if (location == -1) {
return;
}
if (vb.isInstanced()) {
if (!caps.contains(Caps.MeshInstancing)) {
throw new RendererException("Instancing is required, "
+ "but not supported by the "
+ "graphics hardware");
}
}
int slotsRequired = 1;
if (vb.getNumComponents() > 4) {
if (vb.getNumComponents() % 4 != 0) {
throw new RendererException("Number of components in multi-slot "
+ "buffers must be divisible by 4");
}
slotsRequired = vb.getNumComponents() / 4;
}
if (vb.isUpdateNeeded() && idb == null) {
updateBufferData(vb);
}
for (int i = 0; i < slotsRequired; i++) {
gl.glEnableVertexAttribArray(location + i);
}
// NOTE: Use id from interleaved buffer if specified
int bufId = idb != null ? idb.getId() : vb.getId();
assert bufId != -1;
if (context.boundArrayVBO != bufId) {
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId);
context.boundArrayVBO = bufId;
//statistics.onVertexBufferUse(vb, true);
} else {
//statistics.onVertexBufferUse(vb, false);
}
if (slotsRequired == 1) {
gl.glVertexAttribPointer(location,
vb.getNumComponents(),
convertFormat(vb.getFormat()),
vb.isNormalized(),
vb.getStride(),
vb.getOffset());
} else {
for (int i = 0; i < slotsRequired; i++) {
// The pointer maps the next 4 floats in the slot.
// E.g.
// P1: XXXX____________XXXX____________
// P2: ____XXXX____________XXXX________
// P3: ________XXXX____________XXXX____
// P4: ____________XXXX____________XXXX
// stride = 4 bytes in float * 4 floats in slot * num slots
// offset = 4 bytes in float * 4 floats in slot * slot index
gl.glVertexAttribPointer(location + i,
4,
convertFormat(vb.getFormat()),
vb.isNormalized(),
4 * 4 * slotsRequired,
4 * 4 * i);
}
}
for (int i = 0; i < slotsRequired; i++) {
int slot = location + i;
if (vb.isInstanced()) {
glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
}
}
}
public void setVertexAttrib(VertexBuffer vb) { public void setVertexAttrib(VertexBuffer vb) {
setVertexAttrib(vb, null); setVertexAttrib(vb, null);
} }
public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); if (count > 1) {
if (useInstancing) {
glext.glDrawArraysInstancedARB(convertElementMode(mode), 0, glext.glDrawArraysInstancedARB(convertElementMode(mode), 0,
vertCount, count); vertCount, count);
} else { } else {
@ -2503,57 +2617,19 @@ public final class GLRenderer implements Renderer {
int vertCount = mesh.getVertexCount(); int vertCount = mesh.getVertexCount();
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
if (mesh.getMode() == Mode.Hybrid) { if (useInstancing) {
int[] modeStart = mesh.getModeStart(); glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
int[] elementLengths = mesh.getElementLengths(); indexBuf.getData().limit(),
convertFormat(indexBuf.getFormat()),
int elMode = convertElementMode(Mode.Triangles); 0,
int fmt = convertFormat(indexBuf.getFormat()); count);
int elSize = indexBuf.getFormat().getComponentSize();
int listStart = modeStart[0];
int stripStart = modeStart[1];
int fanStart = modeStart[2];
int curOffset = 0;
for (int i = 0; i < elementLengths.length; i++) {
if (i == stripStart) {
elMode = convertElementMode(Mode.TriangleStrip);
} else if (i == fanStart) {
elMode = convertElementMode(Mode.TriangleFan);
}
int elementLength = elementLengths[i];
if (useInstancing) {
glext.glDrawElementsInstancedARB(elMode,
elementLength,
fmt,
curOffset,
count);
} else {
gl.glDrawRangeElements(elMode,
0,
vertCount,
elementLength,
fmt,
curOffset);
}
curOffset += elementLength * elSize;
}
} else { } else {
if (useInstancing) { gl.glDrawRangeElements(convertElementMode(mesh.getMode()),
glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), 0,
indexBuf.getData().limit(), vertCount,
convertFormat(indexBuf.getFormat()), indexBuf.getData().limit(),
0, convertFormat(indexBuf.getFormat()),
count); 0);
} else {
gl.glDrawRangeElements(convertElementMode(mesh.getMode()),
0,
vertCount,
indexBuf.getData().limit(),
convertFormat(indexBuf.getFormat()),
0);
}
} }
} }
@ -2583,27 +2659,45 @@ public final class GLRenderer implements Renderer {
} }
} }
public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) { private void setupVertexBuffersLegacy(Mesh mesh, VertexBuffer[] instanceData) {
int id = mesh.getId(); VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (id == -1) { if (interleavedData != null && interleavedData.isUpdateNeeded()) {
IntBuffer temp = intBuf1; updateBufferData(interleavedData);
gl3.glGenVertexArrays(temp);
id = temp.get(0);
mesh.setId(id);
} }
if (context.boundVertexArray != id) { if (instanceData != null) {
gl3.glBindVertexArray(id); for (VertexBuffer vb : instanceData) {
context.boundVertexArray = id; setVertexAttrib(vb, null);
}
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|| vb.getBufferType() == Type.Index) {
continue;
}
if (vb.getStride() == 0) {
// not interleaved
setVertexAttrib(vb);
} else {
// interleaved
setVertexAttrib(vb, interleavedData);
}
} }
}
private void setupVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) { if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData); updateBufferData(interleavedData);
} }
if (instanceData != null) { if (instanceData != null) {
setVertexAttrib(instanceData, null); for (VertexBuffer vb : instanceData) {
setVertexAttribVAO(vb, null);
}
} }
for (VertexBuffer vb : mesh.getBufferList().getArray()) { for (VertexBuffer vb : mesh.getBufferList().getArray()) {
@ -2615,83 +2709,113 @@ public final class GLRenderer implements Renderer {
if (vb.getStride() == 0) { if (vb.getStride() == 0) {
// not interleaved // not interleaved
setVertexAttrib(vb); setVertexAttribVAO(vb, null);
} else { } else {
// interleaved // interleaved
setVertexAttrib(vb, interleavedData); setVertexAttribVAO(vb, interleavedData);
} }
} }
mesh.clearUpdateNeeded();
} }
private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) { private void updateVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
if (mesh.getId() == -1) { VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
updateVertexArray(mesh, instanceData);
} else { if (instanceData != null) {
// TODO: Check if it was updated for (VertexBuffer vb : instanceData) {
if (vb.isUpdateNeeded()) {
updateBufferData(vb);
}
}
} }
if (context.boundVertexArray != mesh.getId()) { if (interleavedData != null) {
gl3.glBindVertexArray(mesh.getId()); if (interleavedData.isUpdateNeeded()) {
context.boundVertexArray = mesh.getId(); updateBufferData(interleavedData);
}
return;
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|| vb.getBufferType() == Type.Index
|| !vb.isUpdateNeeded()
|| !context.boundShader.isAttributeDefined(vb.getBufferType())) {
continue;
}
updateBufferData(vb);
} }
}
// IntMap<VertexBuffer> buffers = mesh.getBuffers(); private VertexBuffer getIndexBuffer(Mesh mesh, int lod) {
VertexBuffer indices; VertexBuffer indices;
if (mesh.getNumLodLevels() > 0) { if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod); indices = mesh.getLodLevel(lod);
} else { } else {
indices = mesh.getBuffer(Type.Index); indices = mesh.getBuffer(Type.Index);
} }
if (indices != null) { return indices;
drawTriangleList(indices, mesh, count);
} else {
drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
}
clearVertexAttribs();
} }
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { private void setVertexArrayObject(Mesh mesh) {
int id = mesh.getId();
// Here while count is still passed in. Can be removed when/if if (id == -1) {
// the method is collapsed again. -pspeed IntBuffer temp = intBuf1;
count = Math.max(mesh.getInstanceCount(), count); gl3.glGenVertexArrays(temp);
id = temp.get(0);
mesh.setId(id);
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); objManager.registerObject(mesh);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
} }
VertexBuffer indices; if (context.boundVertexArray != id) {
if (mesh.getNumLodLevels() > 0) { gl3.glBindVertexArray(id);
indices = mesh.getLodLevel(lod); context.boundVertexArray = id;
} else {
indices = mesh.getBuffer(Type.Index);
} }
}
if (instanceData != null) { private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
for (VertexBuffer vb : instanceData) { setVertexArrayObject(mesh);
setVertexAttrib(vb, null);
// VAO clears current bound VBO automatically
context.boundElementArrayVBO = 0;
context.boundArrayVBO = 0;
VertexBuffer indices = getIndexBuffer(mesh, lod);
if (mesh.isUpdateNeeded()) {
setupVertexBuffers(mesh, instanceData);
if (indices != null) {
updateBufferData(indices);
}
} else {
updateVertexBuffers(mesh, instanceData);
if (indices != null) {
// NOTE: context.boundElementArrayVBO gets captured in the VAO.
// Make everyone think its already bound.
context.boundElementArrayVBO = indices.getId();
} }
} }
for (VertexBuffer vb : mesh.getBufferList().getArray()) { if (indices != null) {
if (vb.getBufferType() == Type.InterleavedData if (indices.isUpdateNeeded()) {
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers updateBufferData(indices);
|| vb.getBufferType() == Type.Index) {
continue;
} }
if (vb.getStride() == 0) { drawTriangleList(indices, mesh, count);
// not interleaved
setVertexAttrib(vb); context.boundElementArrayVBO = 0;
} else { } else {
// interleaved drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
setVertexAttrib(vb, interleavedData);
}
} }
}
private void renderMeshLegacy(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
setupVertexBuffersLegacy(mesh, instanceData);
VertexBuffer indices = getIndexBuffer(mesh, lod);
clearVertexAttribs(); clearVertexAttribs();
if (indices != null) { if (indices != null) {
drawTriangleList(indices, mesh, count); drawTriangleList(indices, mesh, count);
} else { } else {
@ -2707,7 +2831,7 @@ public final class GLRenderer implements Renderer {
if (count > 1 && !caps.contains(Caps.MeshInstancing)) { if (count > 1 && !caps.contains(Caps.MeshInstancing)) {
throw new RendererException("Mesh instancing is not supported by the video hardware"); throw new RendererException("Mesh instancing is not supported by the video hardware");
} }
//this is kept for backward compatibility.
if (mesh.getLineWidth() != 1f && context.lineWidth != mesh.getLineWidth()) { if (mesh.getLineWidth() != 1f && context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth()); gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth(); context.lineWidth = mesh.getLineWidth();
@ -2717,11 +2841,27 @@ public final class GLRenderer implements Renderer {
gl4.glPatchParameter(mesh.getPatchVertexCount()); gl4.glPatchParameter(mesh.getPatchVertexCount());
} }
statistics.onMeshDrawn(mesh, lod, count); statistics.onMeshDrawn(mesh, lod, count);
// if (ctxCaps.GL_ARB_vertex_array_object){ // Here while count is still passed in. Can be removed when/if
// renderMeshVertexArray(mesh, lod, count); // the method is collapsed again. -pspeed
// }else{ count = Math.max(mesh.getInstanceCount(), count);
renderMeshDefault(mesh, lod, count, instanceData);
// } if (caps.contains(Caps.VertexBufferArray)) {
renderMeshDefault(mesh, lod, count, instanceData);
} else {
renderMeshLegacy(mesh, lod, count, instanceData);
}
}
@Override
public void deleteMesh(Mesh mesh) {
int bufId = mesh.getId();
if (bufId != -1) {
// delete vertex array object
intBuf1.put(0, bufId);
intBuf1.position(0).limit(1);
gl3.glDeleteVertexArrays(intBuf1);
mesh.resetObject();
}
} }
public void setMainFrameBufferSrgb(boolean enableSrgb) { public void setMainFrameBufferSrgb(boolean enableSrgb) {

@ -43,6 +43,7 @@ import com.jme3.math.Matrix4f;
import com.jme3.math.Triangle; import com.jme3.math.Triangle;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Renderer;
import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.VertexBuffer.Usage;
@ -50,6 +51,7 @@ import com.jme3.scene.mesh.*;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap; import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry; import com.jme3.util.IntMap.Entry;
import com.jme3.util.NativeObject;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
@ -74,7 +76,7 @@ import java.util.ArrayList;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
public class Mesh implements Savable, Cloneable, JmeCloneable { public class Mesh extends NativeObject implements Savable, Cloneable, JmeCloneable {
/** /**
* The mode of the Mesh specifies both the type of primitive represented * The mode of the Mesh specifies both the type of primitive represented
@ -129,15 +131,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* with the very first vertex to make a triangle. * with the very first vertex to make a triangle.
*/ */
TriangleFan(false), TriangleFan(false),
/** Reserved(false),
* A combination of various triangle modes. It is best to avoid
* using this mode as it may not be supported by all renderers.
* The {@link Mesh#setModeStart(int[]) mode start points} and
* {@link Mesh#setElementLengths(int[]) element lengths} must
* be specified for this mode.
*/
Hybrid(false),
/** /**
* Used for Tesselation only. Requires to set the number of vertices * Used for Tesselation only. Requires to set the number of vertices
* for each patch (default is 3 for triangle tesselation) * for each patch (default is 3 for triangle tesselation)
@ -185,9 +181,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private int patchVertexCount=3; //only used for tesselation private int patchVertexCount=3; //only used for tesselation
private int maxNumWeights = -1; // only if using skeletal animation private int maxNumWeights = -1; // only if using skeletal animation
private int[] elementLengths;
private int[] modeStart;
private Mode mode = Mode.Triangles; private Mode mode = Mode.Triangles;
/** /**
@ -196,6 +189,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
public Mesh(){ public Mesh(){
} }
protected Mesh(int id) {
super(id);
}
/** /**
* Create a shallow clone of this Mesh. The {@link VertexBuffer vertex * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
* buffers} are shared between this and the clone mesh, the rest * buffers} are shared between this and the clone mesh, the rest
@ -205,23 +202,12 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
@Override @Override
public Mesh clone() { public Mesh clone() {
try { Mesh clone = (Mesh) super.clone();
Mesh clone = (Mesh) super.clone(); clone.meshBound = meshBound.clone();
clone.meshBound = meshBound.clone(); clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.collisionTree = collisionTree != null ? collisionTree : null; clone.buffers = buffers.clone();
clone.buffers = buffers.clone(); clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList); return clone;
clone.vertexArrayID = -1;
if (elementLengths != null) {
clone.elementLengths = elementLengths.clone();
}
if (modeStart != null) {
clone.modeStart = modeStart.clone();
}
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
} }
/** /**
@ -232,37 +218,30 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @return a deep clone of this mesh. * @return a deep clone of this mesh.
*/ */
public Mesh deepClone(){ public Mesh deepClone(){
try{ Mesh clone = (Mesh) super.clone();
Mesh clone = (Mesh) super.clone(); clone.meshBound = meshBound != null ? meshBound.clone() : null;
clone.meshBound = meshBound != null ? meshBound.clone() : null;
// TODO: Collision tree cloning
//clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.collisionTree = null; // it will get re-generated in any case
clone.buffers = new IntMap<VertexBuffer>();
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
for (VertexBuffer vb : buffersList.getArray()){
VertexBuffer bufClone = vb.clone();
clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
clone.buffersList.add(bufClone);
}
clone.vertexArrayID = -1;
clone.vertCount = vertCount;
clone.elementCount = elementCount;
clone.instanceCount = instanceCount;
// although this could change // TODO: Collision tree cloning
// if the bone weight/index buffers are modified //clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.maxNumWeights = maxNumWeights; clone.collisionTree = null; // it will get re-generated in any case
clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; clone.buffers = new IntMap<VertexBuffer>();
clone.modeStart = modeStart != null ? modeStart.clone() : null; clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
return clone; for (VertexBuffer vb : buffersList.getArray()){
}catch (CloneNotSupportedException ex){ VertexBuffer bufClone = vb.clone();
throw new AssertionError(); clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
clone.buffersList.add(bufClone);
} }
clone.vertCount = vertCount;
clone.elementCount = elementCount;
clone.instanceCount = instanceCount;
// although this could change
// if the bone weight/index buffers are modified
clone.maxNumWeights = maxNumWeights;
return clone;
} }
/** /**
@ -302,17 +281,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
} }
/** /**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly. * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/ */
@Override @Override
public Mesh jmeClone() { public Mesh jmeClone() {
try { return (Mesh) super.clone();
Mesh clone = (Mesh)super.clone();
clone.vertexArrayID = -1;
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
} }
/** /**
@ -328,8 +301,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
this.buffersList = cloner.clone(buffersList); this.buffersList = cloner.clone(buffersList);
this.buffers = cloner.clone(buffers); this.buffers = cloner.clone(buffers);
this.lodLevels = cloner.clone(lodLevels); this.lodLevels = cloner.clone(lodLevels);
this.elementLengths = cloner.clone(elementLengths);
this.modeStart = cloner.clone(modeStart);
} }
/** /**
@ -394,26 +365,31 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
public void prepareForAnim(boolean forSoftwareAnim){ public void prepareForAnim(boolean forSoftwareAnim){
if (forSoftwareAnim) { if (forSoftwareAnim) {
// convert indices to ubytes on the heap // convert indices to ubytes on the heap
VertexBuffer indices = getBuffer(Type.BoneIndex); VertexBuffer indices = getBuffer(Type.BoneIndex);
if (!indices.getData().hasArray()) { if (!indices.getData().hasArray()) {
ByteBuffer originalIndex = (ByteBuffer) indices.getData(); ByteBuffer originalIndex = (ByteBuffer) indices.getData();
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
originalIndex.clear(); originalIndex.clear();
arrayIndex.put(originalIndex); arrayIndex.put(originalIndex);
indices.updateData(arrayIndex); indices.updateData(arrayIndex);
} }
indices.setUsage(Usage.CpuOnly); indices.setUsage(Usage.CpuOnly);
// convert weights on the heap // convert weights on the heap
VertexBuffer weights = getBuffer(Type.BoneWeight); VertexBuffer weights = getBuffer(Type.BoneWeight);
if (!weights.getData().hasArray()) { if (!weights.getData().hasArray()) {
FloatBuffer originalWeight = (FloatBuffer) weights.getData(); if (weights.getFormat() == Format.Float) {
FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity()); FloatBuffer originalWeight = (FloatBuffer) weights.getData();
originalWeight.clear(); FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
arrayWeight.put(originalWeight); originalWeight.clear();
weights.updateData(arrayWeight); arrayWeight.put(originalWeight);
} weights.updateData(arrayWeight);
} else {
// UByte to Float conversion
throw new UnsupportedOperationException("Not yet supported");
}
}
weights.setUsage(Usage.CpuOnly); weights.setUsage(Usage.CpuOnly);
// position, normal, and tanget buffers to be in "Stream" mode // position, normal, and tanget buffers to be in "Stream" mode
VertexBuffer positions = getBuffer(Type.Position); VertexBuffer positions = getBuffer(Type.Position);
@ -474,6 +450,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
tangents.setUpdateNeeded(); tangents.setUpdateNeeded();
} }
} }
this.setUpdateNeeded();
} }
/** /**
@ -508,41 +486,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
public VertexBuffer getLodLevel(int lod){ public VertexBuffer getLodLevel(int lod){
return lodLevels[lod]; return lodLevels[lod];
} }
/**
* Get the element lengths for {@link Mode#Hybrid} mesh mode.
*
* @return element lengths
*/
public int[] getElementLengths() {
return elementLengths;
}
/**
* Set the element lengths for {@link Mode#Hybrid} mesh mode.
*
* @param elementLengths The element lengths to set
*/
public void setElementLengths(int[] elementLengths) {
this.elementLengths = elementLengths;
}
/**
* Set the mode start indices for {@link Mode#Hybrid} mesh mode.
*
* @return mode start indices
*/
public int[] getModeStart() {
return modeStart;
}
/**
* Get the mode start indices for {@link Mode#Hybrid} mesh mode.
*/
public void setModeStart(int[] modeStart) {
this.modeStart = modeStart;
}
/** /**
* Returns the mesh mode * Returns the mesh mode
* *
@ -803,19 +747,27 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* {@link #setInterleaved() interleaved} format. * {@link #setInterleaved() interleaved} format.
*/ */
public void updateCounts(){ public void updateCounts(){
if (getBuffer(Type.InterleavedData) != null) // if (getBuffer(Type.InterleavedData) != null) {
throw new IllegalStateException("Should update counts before interleave"); // throw new IllegalStateException("Should update counts before interleave");
// }
VertexBuffer pb = getBuffer(Type.Position); VertexBuffer pb = getBuffer(Type.Position);
VertexBuffer ib = getBuffer(Type.Index); VertexBuffer ib = getBuffer(Type.Index);
if (pb != null){ if (pb != null) {
vertCount = pb.getData().limit() / pb.getNumComponents(); VertexBuffer ip = getBuffer(Type.InterleavedData);
if (ip != null) {
int limitBytes = ip.getData().limit();
int elementSizeWithOthers = pb.getStride();
vertCount = limitBytes / elementSizeWithOthers;
} else {
vertCount = pb.getData().limit() / pb.getNumComponents();
}
} }
if (ib != null){ if (ib != null){
elementCount = computeNumElements(ib.getData().limit()); elementCount = computeNumElements(ib.getData().limit());
}else{ }else{
elementCount = computeNumElements(vertCount); elementCount = computeNumElements(vertCount);
} }
instanceCount = computeInstanceCount(); instanceCount = computeInstanceCount();
} }
@ -940,23 +892,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
indices[2] = ib.get(vertIndex+2); indices[2] = ib.get(vertIndex+2);
} }
/**
* Returns the mesh's VAO ID. Internal use only.
*/
public int getId(){
return vertexArrayID;
}
/**
* Sets the mesh's VAO ID. Internal use only.
*/
public void setId(int id){
if (vertexArrayID != -1)
throw new IllegalStateException("ID has already been set.");
vertexArrayID = id;
}
/** /**
* Generates a collision tree for the mesh. * Generates a collision tree for the mesh.
* Called automatically by {@link #collideWith(com.jme3.collision.Collidable, * Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
@ -1150,9 +1085,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @return A virtual or wrapped index buffer to read the data as a list * @return A virtual or wrapped index buffer to read the data as a list
*/ */
public IndexBuffer getIndicesAsList(){ public IndexBuffer getIndicesAsList(){
if (mode == Mode.Hybrid)
throw new UnsupportedOperationException("Hybrid mode not supported");
IndexBuffer ib = getIndexBuffer(); IndexBuffer ib = getIndexBuffer();
if (ib != null){ if (ib != null){
if (mode.isListMode()){ if (mode.isListMode()){
@ -1426,16 +1358,30 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
return patchVertexCount; return patchVertexCount;
} }
@Override
public void resetObject() {
id = -1;
setUpdateNeeded();
}
@Override
public void deleteObject(Object rendererObject) {
((Renderer)rendererObject).deleteMesh(this);
}
@Override
public NativeObject createDestructableClone() {
return new Mesh(id);
}
@Override
public long getUniqueId() {
return ((long)OBJTYPE_MESH << 32) | ((long)id);
}
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this); OutputCapsule out = ex.getCapsule(this);
// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
// for (Entry<VertexBuffer> buf : buffers){
// if (buf.getValue() != null)
// map.put(buf.getKey()+"a", buf.getValue());
// }
// out.writeStringSavableMap(map, "buffers", null);
out.write(meshBound, "modelBound", null); out.write(meshBound, "modelBound", null);
out.write(vertCount, "vertCount", -1); out.write(vertCount, "vertCount", -1);
out.write(elementCount, "elementCount", -1); out.write(elementCount, "elementCount", -1);
@ -1443,10 +1389,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
out.write(maxNumWeights, "max_num_weights", -1); out.write(maxNumWeights, "max_num_weights", -1);
out.write(mode, "mode", Mode.Triangles); out.write(mode, "mode", Mode.Triangles);
out.write(collisionTree, "collisionTree", null); out.write(collisionTree, "collisionTree", null);
out.write(elementLengths, "elementLengths", null);
out.write(modeStart, "modeStart", null);
out.write(pointSize, "pointSize", 1f); out.write(pointSize, "pointSize", 1f);
//Removing HW skinning buffers to not save them //Removing HW skinning buffers to not save them
VertexBuffer hwBoneIndex = null; VertexBuffer hwBoneIndex = null;
VertexBuffer hwBoneWeight = null; VertexBuffer hwBoneWeight = null;
@ -1480,19 +1424,14 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
instanceCount = in.readInt("instanceCount", -1); instanceCount = in.readInt("instanceCount", -1);
maxNumWeights = in.readInt("max_num_weights", -1); maxNumWeights = in.readInt("max_num_weights", -1);
mode = in.readEnum("mode", Mode.class, Mode.Triangles); mode = in.readEnum("mode", Mode.class, Mode.Triangles);
elementLengths = in.readIntArray("elementLengths", null);
modeStart = in.readIntArray("modeStart", null);
collisionTree = (BIHTree) in.readSavable("collisionTree", null); collisionTree = (BIHTree) in.readSavable("collisionTree", null);
elementLengths = in.readIntArray("elementLengths", null);
modeStart = in.readIntArray("modeStart", null);
pointSize = in.readFloat("pointSize", 1f); pointSize = in.readFloat("pointSize", 1f);
// in.readStringSavableMap("buffers", null);
buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null); buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
for (Entry<VertexBuffer> entry : buffers){ for (Entry<VertexBuffer> entry : buffers){
buffersList.add(entry.getValue()); buffersList.add(entry.getValue());
} }
//creating hw animation buffers empty so that they are put in the cache //creating hw animation buffers empty so that they are put in the cache
if(isAnimated()){ if(isAnimated()){
VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex); VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex);
@ -1502,7 +1441,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
hwBoneWeight.setUsage(Usage.CpuOnly); hwBoneWeight.setUsage(Usage.CpuOnly);
setBuffer(hwBoneWeight); setBuffer(hwBoneWeight);
} }
Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null); Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
if (lodLevelsSavable != null) { if (lodLevelsSavable != null) {
lodLevels = new VertexBuffer[lodLevelsSavable.length]; lodLevels = new VertexBuffer[lodLevelsSavable.length];

@ -75,6 +75,7 @@ public class Node extends Spatial {
* requiresUpdate() method. * requiresUpdate() method.
*/ */
private SafeArrayList<Spatial> updateList = null; private SafeArrayList<Spatial> updateList = null;
/** /**
* False if the update list requires rebuilding. This is Node.class * False if the update list requires rebuilding. This is Node.class
* specific and therefore not included as part of the Spatial update flags. * specific and therefore not included as part of the Spatial update flags.
@ -99,6 +100,7 @@ public class Node extends Spatial {
*/ */
public Node(String name) { public Node(String name) {
super(name); super(name);
// For backwards compatibility, only clear the "requires // For backwards compatibility, only clear the "requires
// update" flag if we are not a subclass of Node. // update" flag if we are not a subclass of Node.
// This prevents subclass from silently failing to receive // This prevents subclass from silently failing to receive
@ -154,6 +156,7 @@ public class Node extends Spatial {
@Override @Override
protected void updateWorldBound(){ protected void updateWorldBound(){
super.updateWorldBound(); super.updateWorldBound();
// for a node, the world bound is a combination of all it's children // for a node, the world bound is a combination of all it's children
// bounds // bounds
BoundingVolume resultBound = null; BoundingVolume resultBound = null;
@ -248,6 +251,7 @@ public class Node extends Spatial {
// This branch has no geometric state that requires updates. // This branch has no geometric state that requires updates.
return; return;
} }
if ((refreshFlags & RF_LIGHTLIST) != 0){ if ((refreshFlags & RF_LIGHTLIST) != 0){
updateWorldLightList(); updateWorldLightList();
} }
@ -262,6 +266,7 @@ public class Node extends Spatial {
} }
refreshFlags &= ~RF_CHILD_LIGHTLIST; refreshFlags &= ~RF_CHILD_LIGHTLIST;
if (!children.isEmpty()) { if (!children.isEmpty()) {
// the important part- make sure child geometric state is refreshed // the important part- make sure child geometric state is refreshed
// first before updating own world bound. This saves // first before updating own world bound. This saves
@ -297,6 +302,7 @@ public class Node extends Spatial {
return count; return count;
} }
/** /**
* <code>getVertexCount</code> returns the number of vertices contained * <code>getVertexCount</code> returns the number of vertices contained
* in all sub-branches of this node that contain geometry. * in all sub-branches of this node that contain geometry.
@ -330,6 +336,7 @@ public class Node extends Spatial {
public int attachChild(Spatial child) { public int attachChild(Spatial child) {
return attachChildAt(child, children.size()); return attachChildAt(child, children.size());
} }
/** /**
* *
* <code>attachChildAt</code> attaches a child to this node at an index. This node * <code>attachChildAt</code> attaches a child to this node at an index. This node
@ -353,6 +360,7 @@ public class Node extends Spatial {
} }
child.setParent(this); child.setParent(this);
children.add(index, child); children.add(index, child);
// XXX: Not entirely correct? Forces bound update up the // XXX: Not entirely correct? Forces bound update up the
// tree stemming from the attached child. Also forces // tree stemming from the attached child. Also forces
// transform update down the tree- // transform update down the tree-
@ -363,8 +371,10 @@ public class Node extends Spatial {
logger.log(Level.FINE,"Child ({0}) attached to this node ({1})", logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
new Object[]{child.getName(), getName()}); new Object[]{child.getName(), getName()});
} }
invalidateUpdateList(); invalidateUpdateList();
} }
return children.size(); return children.size();
} }
@ -439,6 +449,7 @@ public class Node extends Spatial {
child.setTransformRefresh(); child.setTransformRefresh();
// lights are also inherited from parent // lights are also inherited from parent
child.setLightListRefresh(); child.setLightListRefresh();
child.setMatParamOverrideRefresh(); child.setMatParamOverrideRefresh();
invalidateUpdateList(); invalidateUpdateList();
@ -526,6 +537,7 @@ public class Node extends Spatial {
} }
return null; return null;
} }
/** /**
* determines if the provided Spatial is contained in the children list of * determines if the provided Spatial is contained in the children list of
* this node. * this node.
@ -573,32 +585,39 @@ public class Node extends Spatial {
public int collideWith(Collidable other, CollisionResults results){ public int collideWith(Collidable other, CollisionResults results){
int total = 0; int total = 0;
// optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children // 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. // 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 I'm removing this change until some issues can be addressed and I really
think it needs to be implemented a better way anyway. think it needs to be implemented a better way anyway.
First, it causes issues for anyone doing collideWith() with BoundingVolumes First, it causes issues for anyone doing collideWith() with BoundingVolumes
and expecting it to trickle down to the children. For example, children and expecting it to trickle down to the children. For example, children
with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing
a collision check at the parent level then has to do a BoundingSphere to BoundingBox 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 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.) case is tricky and the first sign that this is the wrong approach.)
Second, the rippling changes this caused to 'optimize' collideWith() for this 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 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 idea of calculating a full collision just to see if the two shapes collide at all
is very wasteful. is very wasteful.
A proper implementation should support a simpler boolean check that doesn't do 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% 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 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. 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' 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 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 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 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. enough to do all the time for > 1.
if (children.size() > 4) if (children.size() > 4)
{ {
BoundingVolume bv = this.getWorldBound(); BoundingVolume bv = this.getWorldBound();
@ -691,6 +710,7 @@ public class Node extends Spatial {
// Reset the fields of the clone that should be in a 'new' state. // Reset the fields of the clone that should be in a 'new' state.
nodeClone.updateList = null; nodeClone.updateList = null;
nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone() nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
return nodeClone; return nodeClone;
} }
@ -730,6 +750,7 @@ public class Node extends Spatial {
// cloning this list is fine. // cloning this list is fine.
this.updateList = cloner.clone(updateList); this.updateList = cloner.clone(updateList);
} }
@Override @Override
public void write(JmeExporter e) throws IOException { public void write(JmeExporter e) throws IOException {
super.write(e); super.write(e);
@ -741,6 +762,7 @@ public class Node extends Spatial {
// XXX: Load children before loading itself!! // XXX: Load children before loading itself!!
// This prevents empty children list if controls query // This prevents empty children list if controls query
// it in Control.setSpatial(). // it in Control.setSpatial().
children = new SafeArrayList( Spatial.class, children = new SafeArrayList( Spatial.class,
e.getCapsule(this).readSavableArrayList("children", null) ); e.getCapsule(this).readSavableArrayList("children", null) );
@ -750,6 +772,7 @@ public class Node extends Spatial {
child.parent = this; child.parent = this;
} }
} }
super.read(e); super.read(e);
} }
@ -770,6 +793,7 @@ public class Node extends Spatial {
} }
} }
} }
@Override @Override
public void depthFirstTraversal(SceneGraphVisitor visitor) { public void depthFirstTraversal(SceneGraphVisitor visitor) {
for (Spatial child : children.getArray()) { for (Spatial child : children.getArray()) {
@ -777,6 +801,7 @@ public class Node extends Spatial {
} }
visitor.visit(this); visitor.visit(this);
} }
@Override @Override
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) { protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
queue.addAll(children); queue.addAll(children);

@ -122,10 +122,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/ */
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
RF_BOUND = 0x02, 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_CHILD_LIGHTLIST = 0x08, // some child need geometry update
RF_MATPARAM_OVERRIDE = 0x10; RF_MATPARAM_OVERRIDE = 0x10;
protected CullHint cullHint = CullHint.Inherit; protected CullHint cullHint = CullHint.Inherit;
protected BatchHint batchHint = BatchHint.Inherit; protected BatchHint batchHint = BatchHint.Inherit;
/** /**
@ -137,11 +138,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/ */
protected LightList localLights; protected LightList localLights;
protected transient LightList worldLights; protected transient LightList worldLights;
protected ArrayList<MatParamOverride> localOverrides; protected ArrayList<MatParamOverride> localOverrides;
protected ArrayList<MatParamOverride> worldOverrides; protected ArrayList<MatParamOverride> worldOverrides;
/**
/**
* This spatial's name. * This spatial's name.
*/ */
protected String name; protected String name;
@ -201,6 +200,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/ */
protected Spatial(String name) { protected Spatial(String name) {
this.name = name; this.name = name;
localTransform = new Transform(); localTransform = new Transform();
worldTransform = new Transform(); worldTransform = new Transform();
@ -209,7 +209,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
localOverrides = new ArrayList<MatParamOverride>(); localOverrides = new ArrayList<MatParamOverride>();
worldOverrides = new ArrayList<MatParamOverride>(); worldOverrides = new ArrayList<MatParamOverride>();
refreshFlags |= RF_BOUND; refreshFlags |= RF_BOUND;
} }
@ -230,6 +229,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
boolean requiresUpdates() { boolean requiresUpdates() {
return requiresUpdates | !controls.isEmpty(); 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. * updateLogicalState() to be called even if they contain no controls.
@ -279,15 +279,18 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected void setLightListRefresh() { protected void setLightListRefresh() {
refreshFlags |= RF_LIGHTLIST; refreshFlags |= RF_LIGHTLIST;
// Make sure next updateGeometricState() visits this branch // Make sure next updateGeometricState() visits this branch
// to update lights. // to update lights.
Spatial p = parent; Spatial p = parent;
while (p != null) { while (p != null) {
if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) { if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
// The parent already has this flag, // The parent already has this flag,
// so must all ancestors. // so must all ancestors.
return; return;
} }
p.refreshFlags |= RF_CHILD_LIGHTLIST; p.refreshFlags |= RF_CHILD_LIGHTLIST;
p = p.parent; p = p.parent;
} }
@ -305,7 +308,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
p = p.parent; p = p.parent;
} }
} }
/** /**
* Indicate that the bounding of this spatial has changed and that * Indicate that the bounding of this spatial has changed and that
* a refresh is required. * a refresh is required.
@ -323,6 +325,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
p = p.parent; p = p.parent;
} }
} }
/** /**
* (Internal use only) Forces a refresh of the given types of data. * (Internal use only) Forces a refresh of the given types of data.
* *
@ -552,8 +555,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
Vector3f compVecA = vars.vect4; Vector3f compVecA = vars.vect4;
compVecA.set(position).subtractLocal(worldTranslation); compVecA.set(position).subtractLocal(worldTranslation);
getLocalRotation().lookAt(compVecA, upVector); getLocalRotation().lookAt(compVecA, upVector);
if ( getParent() != null ) { if ( getParent() != null ) {
Quaternion rot=vars.quat1; Quaternion rot=vars.quat1;
rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
@ -636,7 +641,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
} }
localOverrides.clear(); localOverrides.clear();
} }
/** /**
* Should only be called from updateGeometricState(). * Should only be called from updateGeometricState().
* In most cases should not be subclassed. * In most cases should not be subclassed.
@ -769,6 +773,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
controls.add(control); controls.add(control);
control.setSpatial(this); control.setSpatial(this);
boolean after = requiresUpdates(); boolean after = requiresUpdates();
// If the requirement to be updated has changed // If the requirement to be updated has changed
// then we need to let the parent node know so it // then we need to let the parent node know so it
// can rebuild its update list. // can rebuild its update list.
@ -792,6 +797,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
} }
} }
boolean after = requiresUpdates(); boolean after = requiresUpdates();
// If the requirement to be updated has changed // If the requirement to be updated has changed
// then we need to let the parent node know so it // then we need to let the parent node know so it
// can rebuild its update list. // can rebuild its update list.
@ -817,12 +823,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
} }
boolean after = requiresUpdates(); boolean after = requiresUpdates();
// If the requirement to be updated has changed // If the requirement to be updated has changed
// then we need to let the parent node know so it // then we need to let the parent node know so it
// can rebuild its update list. // can rebuild its update list.
if( parent != null && before != after ) { if( parent != null && before != after ) {
parent.invalidateUpdateList(); parent.invalidateUpdateList();
} }
return result; return result;
} }
@ -907,6 +915,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
if ((refreshFlags & RF_BOUND) != 0) { if ((refreshFlags & RF_BOUND) != 0) {
updateWorldBound(); updateWorldBound();
} }
if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
updateMatParamOverrides(); updateMatParamOverrides();
} }
@ -1391,7 +1400,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
for (MatParamOverride override : localOverrides) { for (MatParamOverride override : localOverrides) {
clone.localOverrides.add((MatParamOverride) override.clone()); clone.localOverrides.add((MatParamOverride) override.clone());
} }
// No need to force cloned to update. // No need to force cloned to update.
// This node already has the refresh flags // This node already has the refresh flags
// set below so it will have to update anyway. // set below so it will have to update anyway.
@ -1469,6 +1477,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
// the transforms and stuff get refreshed. // the transforms and stuff get refreshed.
clone.setTransformRefresh(); clone.setTransformRefresh();
clone.setLightListRefresh(); clone.setLightListRefresh();
clone.setMatParamOverrideRefresh();
return clone; return clone;
} }
@ -1621,12 +1630,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
localLights = (LightList) ic.readSavable("lights", null); localLights = (LightList) ic.readSavable("lights", null);
localLights.setOwner(this); localLights.setOwner(this);
localOverrides = ic.readSavableArrayList("overrides", null); localOverrides = ic.readSavableArrayList("overrides", null);
if (localOverrides == null) { if (localOverrides == null) {
localOverrides = new ArrayList<MatParamOverride>(); localOverrides = new ArrayList<>();
} }
worldOverrides = new ArrayList<MatParamOverride>(); worldOverrides = new ArrayList<>();
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split //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 AnimControl creates the SkeletonControl for old files and add it to the spatial.

@ -524,6 +524,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
this.usage = usage; this.usage = usage;
} }
/**
* The size of an element in bytes.
*
* The number of components multiplied by the size of a component.
*
* @return size of an element in bytes.
*/
public int getElementSize() {
return componentsLength;
}
/** /**
* @param normalized Set to true if integer components should be converted * @param normalized Set to true if integer components should be converted
* from their maximal range into the range 0.0 - 1.0 when converted to * from their maximal range into the range 0.0 - 1.0 when converted to
@ -976,6 +987,10 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* of the parameters. The buffer will be of the type specified by * of the parameters. The buffer will be of the type specified by
* {@link Format format} and would be able to contain the given number * {@link Format format} and would be able to contain the given number
* of elements with the given number of components in each element. * of elements with the given number of components in each element.
* @param format The format of the buffer to create
* @param components The number of components (aka dimensions)
* @param numElements Capacity of the buffer in number of elements.
* @return A buffer satisfying the given requirements.
*/ */
public static Buffer createBuffer(Format format, int components, int numElements){ public static Buffer createBuffer(Format format, int components, int numElements){
if (components < 1 || components > 4) if (components < 1 || components > 4)
@ -1010,7 +1025,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* @return Deep clone of this buffer * @return Deep clone of this buffer
*/ */
@Override @Override
public VertexBuffer clone(){ public VertexBuffer clone() {
// NOTE: Superclass GLObject automatically creates shallow clone // NOTE: Superclass GLObject automatically creates shallow clone
// e.g re-use ID. // e.g re-use ID.
VertexBuffer vb = (VertexBuffer) super.clone(); VertexBuffer vb = (VertexBuffer) super.clone();

@ -81,7 +81,7 @@ public class VirtualIndexBuffer extends IndexBuffer {
case Triangles: case Triangles:
numIndices = numVerts; numIndices = numVerts;
return; return;
case Hybrid: case Reserved:
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

@ -303,6 +303,16 @@ public final class Shader extends NativeObject {
return attrib; return attrib;
} }
public boolean isAttributeDefined(VertexBuffer.Type attribType) {
int ordinal = attribType.ordinal();
Attribute attrib = attribs.get(ordinal);
if (attrib == null){
return false;
} else {
return attrib.location != -1 && attrib.location != 2;
}
}
public ListMap<String, Uniform> getUniformMap(){ public ListMap<String, Uniform> getUniformMap(){
return uniforms; return uniforms;
} }

@ -59,7 +59,7 @@ public abstract class ShaderGenerator {
/** /**
* the technique def to use for the shader generation * the technique def to use for the shader generation
*/ */
protected TechniqueDef techniqueDef = null; protected TechniqueDef techniqueDef = null;
/** /**
* Build a shaderGenerator * Build a shaderGenerator
@ -67,16 +67,17 @@ public abstract class ShaderGenerator {
* @param assetManager * @param assetManager
*/ */
protected ShaderGenerator(AssetManager assetManager) { protected ShaderGenerator(AssetManager assetManager) {
this.assetManager = assetManager; this.assetManager = assetManager;
} }
public void initialize(TechniqueDef techniqueDef) {
public void initialize(TechniqueDef techniqueDef){
this.techniqueDef = techniqueDef; this.techniqueDef = techniqueDef;
} }
/** /**
* Generate vertex and fragment shaders for the given technique * Generate vertex and fragment shaders for the given technique
* *
* @param definesSourceCode Defines to include alongside the shader. May be
* null.
* @return a Shader program * @return a Shader program
*/ */
public Shader generateShader(String definesSourceCode) { public Shader generateShader(String definesSourceCode) {
@ -94,13 +95,13 @@ public abstract class ShaderGenerator {
String extension = type.getExtension(); String extension = type.getExtension();
String language = getLanguageAndVersion(type); String language = getLanguageAndVersion(type);
String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type); String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
if (shaderSourceCode != null) { if (shaderSourceCode != null) {
String shaderSourceAssetName = techniqueName + "." + extension; String shaderSourceAssetName = techniqueName + "." + extension;
shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language); shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
} }
} }
techniqueDef = null; techniqueDef = null;
return shader; return shader;
} }
@ -114,14 +115,13 @@ public abstract class ShaderGenerator {
* @return the code of the generated vertex shader * @return the code of the generated vertex shader
*/ */
protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) { protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
if (type == ShaderType.TessellationControl || if (type == ShaderType.TessellationControl
type == ShaderType.TessellationEvaluation || || type == ShaderType.TessellationEvaluation
type == ShaderType.Geometry) { || type == ShaderType.Geometry) {
// TODO: Those are not supported. // TODO: Those are not supported.
// Too much code assumes that type is either Vertex or Fragment // Too much code assumes that type is either Vertex or Fragment
return null; return null;
} }
indent = 0; indent = 0;
StringBuilder sourceDeclaration = new StringBuilder(); StringBuilder sourceDeclaration = new StringBuilder();
@ -192,8 +192,8 @@ public abstract class ShaderGenerator {
if (loadedSource.length() > 1) { if (loadedSource.length() > 1) {
loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}")); loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}"));
String[] sourceParts = loadedSource.split("\\s*void\\s*main\\s*\\(\\s*\\)\\s*\\{"); String[] sourceParts = loadedSource.split("\\s*void\\s*main\\s*\\(\\s*\\)\\s*\\{");
if(sourceParts.length<2){ if (sourceParts.length < 2) {
throw new IllegalArgumentException("Syntax error in "+ shaderPath +". Cannot find 'void main(){' in \n"+ loadedSource); throw new IllegalArgumentException("Syntax error in " + shaderPath + ". Cannot find 'void main(){' in \n" + loadedSource);
} }
generateDeclarativeSection(sourceDeclaration, shaderNode, sourceParts[0], info); generateDeclarativeSection(sourceDeclaration, shaderNode, sourceParts[0], info);
generateNodeMainSection(source, shaderNode, sourceParts[1], info); generateNodeMainSection(source, shaderNode, sourceParts[1], info);
@ -250,7 +250,7 @@ public abstract class ShaderGenerator {
* *
* @see ShaderNode#getDefinition() * @see ShaderNode#getDefinition()
* @see ShaderNodeDefinition#getType() * @see ShaderNodeDefinition#getType()
* *
* @param nodeDecalarationSource the declaration part of the node * @param nodeDecalarationSource the declaration part of the node
* @param source the StringBuilder to append generated code. * @param source the StringBuilder to append generated code.
* @param shaderNode the shaderNode. * @param shaderNode the shaderNode.
@ -319,5 +319,5 @@ public abstract class ShaderGenerator {
} }
} }
return index; return index;
} }
} }

@ -164,4 +164,7 @@ public class NullRenderer implements Renderer {
public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
} }
@Override
public void deleteMesh(Mesh mesh) {
}
} }

@ -299,7 +299,21 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
* *
* Requires {@link Caps#TextureCompressionETC1}. * Requires {@link Caps#TextureCompressionETC1}.
*/ */
ETC1(4, false, true, false); ETC1(4, false, true, false),
/**
* RGTC with red channel only.
*
* Requires {@link Caps#TextureCompressionRGTC}.
*/
RTC(4, false, true, false),
/**
* RGTC with red and green channels.
*
* Requires {@link Caps#TextureCompressionRGTC}.
*/
RGTC(8, false, true, false);
private int bpp; private int bpp;
private boolean isDepth; private boolean isDepth;

@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable {
OBJTYPE_SHADERSOURCE = 5, OBJTYPE_SHADERSOURCE = 5,
OBJTYPE_AUDIOBUFFER = 6, OBJTYPE_AUDIOBUFFER = 6,
OBJTYPE_AUDIOSTREAM = 7, OBJTYPE_AUDIOSTREAM = 7,
OBJTYPE_FILTER = 8; OBJTYPE_FILTER = 8,
OBJTYPE_MESH = 9;
/** /**
* The object manager to which this NativeObject is registered to. * The object manager to which this NativeObject is registered to.

@ -22,5 +22,5 @@ LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib
LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx LOADER com.jme3.scene.plugins.fbx.FbxLoader : fbx
LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba # LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba

@ -85,6 +85,8 @@ public class DDSLoader implements AssetLoader {
private static final int PF_DXT1 = 0x31545844; private static final int PF_DXT1 = 0x31545844;
private static final int PF_DXT3 = 0x33545844; private static final int PF_DXT3 = 0x33545844;
private static final int PF_DXT5 = 0x35545844; private static final int PF_DXT5 = 0x35545844;
private static final int PF_ETC1 = 0x31435445;
private static final int PF_ETC_ = 0x20435445; // the underscore represents a space
private static final int PF_ATI1 = 0x31495441; private static final int PF_ATI1 = 0x31495441;
private static final int PF_ATI2 = 0x32495441; // 0x41544932; private static final int PF_ATI2 = 0x32495441; // 0x41544932;
private static final int PF_DX10 = 0x30315844; // a DX10 format private static final int PF_DX10 = 0x30315844; // a DX10 format
@ -94,6 +96,9 @@ public class DDSLoader implements AssetLoader {
DX10DIM_TEXTURE3D = 0x4; DX10DIM_TEXTURE3D = 0x4;
private static final int DX10MISC_GENERATE_MIPS = 0x1, private static final int DX10MISC_GENERATE_MIPS = 0x1,
DX10MISC_TEXTURECUBE = 0x4; DX10MISC_TEXTURECUBE = 0x4;
private static final int DXGI_FORMAT_BC4_TYPELESS = 79;
private static final int DXGI_FORMAT_BC4_UNORM = 80;
private static final int DXGI_FORMAT_BC4_SNORM = 81;
private static final double LOG2 = Math.log(2); private static final double LOG2 = Math.log(2);
private int width; private int width;
private int height; private int height;
@ -105,9 +110,11 @@ public class DDSLoader implements AssetLoader {
private int caps2; private int caps2;
private boolean directx10; private boolean directx10;
private boolean compressed; private boolean compressed;
private boolean dxtOrRgtc;
private boolean texture3D; private boolean texture3D;
private boolean grayscaleOrAlpha; private boolean grayscaleOrAlpha;
private boolean normal; private boolean normal;
private ColorSpace colorSpace;
private Format pixelFormat; private Format pixelFormat;
private int bpp; private int bpp;
private int[] sizes; private int[] sizes;
@ -133,7 +140,8 @@ public class DDSLoader implements AssetLoader {
((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap); ((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap);
} }
ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY()); ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB);
return new Image(pixelFormat, width, height, depth, data, sizes, colorSpace);
} finally { } finally {
if (stream != null){ if (stream != null){
stream.close(); stream.close();
@ -145,18 +153,24 @@ public class DDSLoader implements AssetLoader {
in = new LittleEndien(stream); in = new LittleEndien(stream);
loadHeader(); loadHeader();
ArrayList<ByteBuffer> data = readData(false); ArrayList<ByteBuffer> data = readData(false);
return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB); return new Image(pixelFormat, width, height, depth, data, sizes, colorSpace);
} }
private void loadDX10Header() throws IOException { private void loadDX10Header() throws IOException {
int dxgiFormat = in.readInt(); int dxgiFormat = in.readInt();
if (dxgiFormat == 0) { if (dxgiFormat == 0) {
pixelFormat = Format.ETC1; pixelFormat = Format.ETC1;
bpp = 4; compressed = true;
bpp = 4;
} else { } else {
pixelFormat = null; // DXGIFormat.getJmeFormat(dxgiFormat);
if (pixelFormat == null) {
throw new IOException("Unsupported DX10 format: " + dxgiFormat); throw new IOException("Unsupported DX10 format: " + dxgiFormat);
}
bpp = pixelFormat.getBitsPerPixel();
compressed = pixelFormat.isCompressed();
} }
compressed = true;
int resDim = in.readInt(); int resDim = in.readInt();
if (resDim == DX10DIM_TEXTURE3D) { if (resDim == DX10DIM_TEXTURE3D) {
@ -201,6 +215,7 @@ public class DDSLoader implements AssetLoader {
caps2 = in.readInt(); caps2 = in.readInt();
in.skipBytes(12); in.skipBytes(12);
texture3D = false; texture3D = false;
colorSpace = ColorSpace.sRGB;
if (!directx10) { if (!directx10) {
if (!is(caps1, DDSCAPS_TEXTURE)) { if (!is(caps1, DDSCAPS_TEXTURE)) {
@ -268,10 +283,12 @@ public class DDSLoader implements AssetLoader {
} else { } else {
pixelFormat = Image.Format.DXT1; pixelFormat = Image.Format.DXT1;
} }
dxtOrRgtc = true;
break; break;
case PF_DXT3: case PF_DXT3:
bpp = 8; bpp = 8;
pixelFormat = Image.Format.DXT3; pixelFormat = Image.Format.DXT3;
dxtOrRgtc = true;
break; break;
case PF_DXT5: case PF_DXT5:
bpp = 8; bpp = 8;
@ -279,17 +296,24 @@ public class DDSLoader implements AssetLoader {
if (swizzle == SWIZZLE_xGxR) { if (swizzle == SWIZZLE_xGxR) {
normal = true; normal = true;
} }
dxtOrRgtc = true;
break; break;
/*
case PF_ATI1: case PF_ATI1:
bpp = 4; bpp = 4;
pixelFormat = Image.Format.LTC; pixelFormat = Image.Format.RTC;
dxtOrRgtc = true;
break; break;
case PF_ATI2: case PF_ATI2:
bpp = 8; bpp = 8;
pixelFormat = Image.Format.LATC; pixelFormat = Image.Format.RGTC;
dxtOrRgtc = true;
break;
case PF_ETC1:
case PF_ETC_:
bpp = 4;
pixelFormat = Image.Format.ETC1;
dxtOrRgtc = false;
break; break;
*/
case PF_DX10: case PF_DX10:
compressed = false; compressed = false;
directx10 = true; directx10 = true;
@ -530,6 +554,30 @@ public class DDSLoader implements AssetLoader {
return dataBuffer; return dataBuffer;
} }
public ByteBuffer readCompressed2Dor3D(boolean flip, int totalSize) throws IOException {
logger.log(Level.FINEST, "Source image format: {0}", pixelFormat);
ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth);
// TODO: add support for flipping ETC1
for (int i = 0; i < depth; i++) {
int mipWidth = width;
int mipHeight = height;
for (int mip = 0; mip < mipMapCount; mip++) {
byte[] data = new byte[sizes[mip]];
in.readFully(data);
buffer.put(data);
mipWidth = Math.max(mipWidth / 2, 1);
mipHeight = Math.max(mipHeight / 2, 1);
}
}
buffer.rewind();
return buffer;
}
/** /**
* Reads a DXT compressed image from the InputStream * Reads a DXT compressed image from the InputStream
* *
@ -738,8 +786,10 @@ public class DDSLoader implements AssetLoader {
ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>(); ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
if (depth > 1 && !texture3D) { if (depth > 1 && !texture3D) {
for (int i = 0; i < depth; i++) { for (int i = 0; i < depth; i++) {
if (compressed) { if (compressed && dxtOrRgtc) {
allMaps.add(readDXT2D(flip, totalSize)); allMaps.add(readDXT2D(flip, totalSize));
} else if (compressed) {
allMaps.add(readCompressed2Dor3D(flip, totalSize));
} else if (grayscaleOrAlpha) { } else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale2D(flip, totalSize)); allMaps.add(readGrayscale2D(flip, totalSize));
} else { } else {
@ -747,8 +797,10 @@ public class DDSLoader implements AssetLoader {
} }
} }
} else if (texture3D) { } else if (texture3D) {
if (compressed) { if (compressed && dxtOrRgtc) {
allMaps.add(readDXT3D(flip, totalSize)); allMaps.add(readDXT3D(flip, totalSize));
} else if (compressed) {
allMaps.add(readCompressed2Dor3D(flip, totalSize));
} else if (grayscaleOrAlpha) { } else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale3D(flip, totalSize)); allMaps.add(readGrayscale3D(flip, totalSize));
} else { } else {
@ -756,8 +808,10 @@ public class DDSLoader implements AssetLoader {
} }
} else { } else {
if (compressed) { if (compressed && dxtOrRgtc) {
allMaps.add(readDXT2D(flip, totalSize)); allMaps.add(readDXT2D(flip, totalSize));
} else if (compressed) {
allMaps.add(readCompressed2Dor3D(flip, totalSize));
} else if (grayscaleOrAlpha) { } else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale2D(flip, totalSize)); allMaps.add(readGrayscale2D(flip, totalSize));
} else { } else {
@ -822,7 +876,7 @@ public class DDSLoader implements AssetLoader {
buf.append((char) (value & 0xFF)); buf.append((char) (value & 0xFF));
buf.append((char) ((value & 0xFF00) >> 8)); buf.append((char) ((value & 0xFF00) >> 8));
buf.append((char) ((value & 0xFF0000) >> 16)); buf.append((char) ((value & 0xFF0000) >> 16));
buf.append((char) ((value & 0xFF00000) >> 24)); buf.append((char) ((value & 0xFF000000) >> 24));
return buf.toString(); return buf.toString();
} }

@ -213,20 +213,18 @@ public class DXTFlipper {
case DXT5: case DXT5:
type = 3; type = 3;
break; break;
/* case RGTC:
case LATC:
type = 4; type = 4;
break; break;
case LTC: case RTC:
type = 5; type = 5;
break; break;
*/
default: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
// DXT1 uses 8 bytes per block, // DXT1 uses 8 bytes per block,
// DXT3, DXT5, LATC use 16 bytes per block // DXT3, DXT5, RGTC use 16 bytes per block
int bpb = type == 1 || type == 5 ? 8 : 16; int bpb = type == 1 || type == 5 ? 8 : 16;
ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb); ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);

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

@ -0,0 +1,60 @@
package com.jme3.app;
import com.jme3.IntegrationTest;
import com.jme3.scene.Mesh;
import com.jme3.system.AppSettings;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.prefs.BackingStoreException;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.experimental.categories.Category;
@Category(IntegrationTest.class)
public class AppSettingsIT {
private static final String APPSETTINGS_KEY = "JME_AppSettingsTest";
@Test
public void testPreferencesSaveLoad() throws BackingStoreException {
AppSettings settings = new AppSettings(false);
settings.putBoolean("TestBool", true);
settings.putInteger("TestInt", 123);
settings.putString("TestStr", "HelloWorld");
settings.putFloat("TestFloat", 123.567f);
settings.put("TestObj", new Mesh()); // Objects not supported by preferences
settings.save(APPSETTINGS_KEY);
AppSettings loadedSettings = new AppSettings(false);
loadedSettings.load(APPSETTINGS_KEY);
assertEquals(true, loadedSettings.getBoolean("TestBool"));
assertEquals(123, loadedSettings.getInteger("TestInt"));
assertEquals("HelloWorld", loadedSettings.getString("TestStr"));
assertEquals(123.567f, loadedSettings.get("TestFloat"));
}
@Test
public void testStreamSaveLoad() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
AppSettings settings = new AppSettings(false);
settings.putBoolean("TestBool", true);
settings.putInteger("TestInt", 123);
settings.putString("TestStr", "HelloWorld");
settings.putFloat("TestFloat", 123.567f);
settings.put("TestObj", new Mesh()); // Objects not supported by file settings
settings.save(baos);
AppSettings loadedSettings = new AppSettings(false);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
loadedSettings.load(bais);
assertEquals(true, loadedSettings.getBoolean("TestBool"));
assertEquals(123, loadedSettings.getInteger("TestInt"));
assertEquals("HelloWorld", loadedSettings.getString("TestStr"));
assertEquals(123.567f, loadedSettings.get("TestFloat"));
}
}

@ -4,4 +4,5 @@ if (!hasProperty('mainClass')) {
dependencies { dependencies {
compile project(':jme3-core') compile project(':jme3-core')
testCompile project(path: ':jme3-core', configuration: 'testOutput')
} }

@ -35,6 +35,8 @@ import java.io.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -44,9 +46,9 @@ import java.util.logging.Logger;
/** /**
* Utility class to register, extract, and load native libraries. * Utility class to register, extract, and load native libraries.
* <br> * <br>
* Register your own libraries via the {@link #registerNativeLibrary(String, Platform, String, String)} method, for * Register your own libraries via the
* each platform. * {@link #registerNativeLibrary(String, Platform, String, String)} method, for
* You can then extract this library (depending on platform), by * each platform. You can then extract this library (depending on platform), by
* using {@link #loadNativeLibrary(java.lang.String, boolean) }. * using {@link #loadNativeLibrary(java.lang.String, boolean) }.
* <br> * <br>
* Example:<br> * Example:<br>
@ -62,80 +64,82 @@ import java.util.logging.Logger;
* This will register the library. Load it via: <br> * This will register the library. Load it via: <br>
* <code><pre> * <code><pre>
* NativeLibraryLoader.loadNativeLibrary("mystuff", true); * NativeLibraryLoader.loadNativeLibrary("mystuff", true);
* </pre></code> * </pre></code> It will load the right library automatically based on the
* It will load the right library automatically based on the platform. * platform.
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
public final class NativeLibraryLoader { public final class NativeLibraryLoader {
private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName()); private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
private static final byte[] buf = new byte[1024 * 100]; private static final byte[] buf = new byte[1024 * 100];
private static File extractionFolderOverride = null; private static File extractionFolderOverride = null;
private static File extractionFolder = null; private static File extractionFolder = null;
private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap
= new HashMap<NativeLibrary.Key, NativeLibrary>(); = new HashMap<NativeLibrary.Key, NativeLibrary>();
/** /**
* Register a new known library. * Register a new known library.
* *
* This simply registers a known library, the actual extraction and loading * This simply registers a known library, the actual extraction and loading
* is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }. * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean)
* * }.
* @param name The name / ID of the library (not OS or architecture specific). *
* @param platform The platform for which the in-natives-jar path has * @param name The name / ID of the library (not OS or architecture
* been specified for. * specific).
* @param path The path inside the natives-jar or classpath * @param platform The platform for which the in-natives-jar path has been
* corresponding to this library. Must be compatible with the platform * specified for.
* argument. * @param path The path inside the natives-jar or classpath corresponding to
* @param extractAsName The filename that the library should be extracted as, * this library. Must be compatible with the platform argument.
* if null, use the same name as in the path. * @param extractAsName The filename that the library should be extracted
* as, if null, use the same name as in the path.
*/ */
public static void registerNativeLibrary(String name, Platform platform, public static void registerNativeLibrary(String name, Platform platform,
String path, String extractAsName) { String path, String extractAsName) {
nativeLibraryMap.put(new NativeLibrary.Key(name, platform), nativeLibraryMap.put(new NativeLibrary.Key(name, platform),
new NativeLibrary(name, platform, path, extractAsName)); new NativeLibrary(name, platform, path, extractAsName));
} }
/** /**
* Register a new known JNI library. * Register a new known JNI library.
* *
* This simply registers a known library, the actual extraction and loading * This simply registers a known library, the actual extraction and loading
* is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }. * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean)
* * }.
* This method should be called several times for each library name, *
* each time specifying a different platform + path combination. * This method should be called several times for each library name, each
* * time specifying a different platform + path combination.
* @param name The name / ID of the library (not OS or architecture specific). *
* @param platform The platform for which the in-natives-jar path has * @param name The name / ID of the library (not OS or architecture
* been specified for. * specific).
* @param path The path inside the natives-jar or classpath * @param platform The platform for which the in-natives-jar path has been
* corresponding to this library. Must be compatible with the platform * specified for.
* argument. * @param path The path inside the natives-jar or classpath corresponding to
* this library. Must be compatible with the platform argument.
*/ */
public static void registerNativeLibrary(String name, Platform platform, public static void registerNativeLibrary(String name, Platform platform,
String path) { String path) {
registerNativeLibrary(name, platform, path, null); registerNativeLibrary(name, platform, path, null);
} }
static { static {
// LWJGL // LWJGL
registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl.dll"); registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl.dll");
registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl64.dll"); registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl64.dll");
registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl.so"); registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl.so");
registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl64.so"); registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl64.so");
registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib"); registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib");
registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib"); registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib");
// OpenAL // OpenAL
// For OSX: Need to add lib prefix when extracting // For OSX: Need to add lib prefix when extracting
registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll"); registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll");
registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL64.dll"); registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL64.dll");
registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal.so"); registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal.so");
registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal64.so"); registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal64.so");
registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib"); registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib");
registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib"); registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib");
// LWJGL 3.x // LWJGL 3.x
registerNativeLibrary("lwjgl3", Platform.Windows32, "native/windows/lwjgl32.dll"); registerNativeLibrary("lwjgl3", Platform.Windows32, "native/windows/lwjgl32.dll");
@ -173,39 +177,39 @@ public final class NativeLibraryLoader {
// BulletJme // BulletJme
registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll"); registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll");
registerNativeLibrary("bulletjme", Platform.Windows64, "native/windows/x86_64/bulletjme.dll"); registerNativeLibrary("bulletjme", Platform.Windows64, "native/windows/x86_64/bulletjme.dll");
registerNativeLibrary("bulletjme", Platform.Linux32, "native/linux/x86/libbulletjme.so"); registerNativeLibrary("bulletjme", Platform.Linux32, "native/linux/x86/libbulletjme.so");
registerNativeLibrary("bulletjme", Platform.Linux64, "native/linux/x86_64/libbulletjme.so"); registerNativeLibrary("bulletjme", Platform.Linux64, "native/linux/x86_64/libbulletjme.so");
registerNativeLibrary("bulletjme", Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib"); registerNativeLibrary("bulletjme", Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib");
registerNativeLibrary("bulletjme", Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib"); registerNativeLibrary("bulletjme", Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib");
// JInput // JInput
// For OSX: Need to rename extension jnilib -> dylib when extracting // For OSX: Need to rename extension jnilib -> dylib when extracting
registerNativeLibrary("jinput", Platform.Windows32, "native/windows/jinput-raw.dll"); registerNativeLibrary("jinput", Platform.Windows32, "native/windows/jinput-raw.dll");
registerNativeLibrary("jinput", Platform.Windows64, "native/windows/jinput-raw_64.dll"); registerNativeLibrary("jinput", Platform.Windows64, "native/windows/jinput-raw_64.dll");
registerNativeLibrary("jinput", Platform.Linux32, "native/windows/libjinput-linux.so"); registerNativeLibrary("jinput", Platform.Linux32, "native/windows/libjinput-linux.so");
registerNativeLibrary("jinput", Platform.Linux64, "native/windows/libjinput-linux64.so"); registerNativeLibrary("jinput", Platform.Linux64, "native/windows/libjinput-linux64.so");
registerNativeLibrary("jinput", Platform.MacOSX32, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib"); registerNativeLibrary("jinput", Platform.MacOSX32, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib");
registerNativeLibrary("jinput", Platform.MacOSX64, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib"); registerNativeLibrary("jinput", Platform.MacOSX64, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib");
// JInput Auxiliary (only required on Windows) // JInput Auxiliary (only required on Windows)
registerNativeLibrary("jinput-dx8", Platform.Windows32, "native/windows/jinput-dx8.dll"); registerNativeLibrary("jinput-dx8", Platform.Windows32, "native/windows/jinput-dx8.dll");
registerNativeLibrary("jinput-dx8", Platform.Windows64, "native/windows/jinput-dx8_64.dll"); registerNativeLibrary("jinput-dx8", Platform.Windows64, "native/windows/jinput-dx8_64.dll");
registerNativeLibrary("jinput-dx8", Platform.Linux32, null); registerNativeLibrary("jinput-dx8", Platform.Linux32, null);
registerNativeLibrary("jinput-dx8", Platform.Linux64, null); registerNativeLibrary("jinput-dx8", Platform.Linux64, null);
registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null); registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null);
registerNativeLibrary("jinput-dx8", Platform.MacOSX64, null); registerNativeLibrary("jinput-dx8", Platform.MacOSX64, null);
} }
private NativeLibraryLoader() { private NativeLibraryLoader() {
} }
/** /**
* Determine if native bullet is on the classpath. * Determine if native bullet is on the classpath.
* *
* Currently the context extracts the native bullet libraries, so * Currently the context extracts the native bullet libraries, so this
* this method is needed to determine if it is needed. * method is needed to determine if it is needed. Ideally, native bullet
* Ideally, native bullet should be responsible for its own natives. * should be responsible for its own natives.
* *
* @return True native bullet is on the classpath, false otherwise. * @return True native bullet is on the classpath, false otherwise.
*/ */
public static boolean isUsingNativeBullet() { public static boolean isUsingNativeBullet() {
@ -216,35 +220,37 @@ public final class NativeLibraryLoader {
return false; return false;
} }
} }
/** /**
* Specify a custom location where native libraries should * Specify a custom location where native libraries should be extracted to.
* be extracted to. Ensure this is a unique path not used * Ensure this is a unique path not used by other applications to extract
* by other applications to extract their libraries. * their libraries. Set to <code>null</code> to restore default
* Set to <code>null</code> to restore default
* functionality. * functionality.
* *
* @param path Path where to extract native libraries. * @param path Path where to extract native libraries.
*/ */
public static void setCustomExtractionFolder(String path) { public static void setCustomExtractionFolder(String path) {
extractionFolderOverride = new File(path).getAbsoluteFile(); if (path != null) {
extractionFolderOverride = new File(path).getAbsoluteFile();
} else {
extractionFolderOverride = null;
}
} }
/** /**
* Returns the folder where native libraries will be extracted. * Returns the folder where native libraries will be extracted. This is
* This is automatically determined at run-time based on the * automatically determined at run-time based on the following criteria:<br>
* following criteria:<br>
* <ul> * <ul>
* <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom * <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
* extraction folder} has been specified, it is returned. * extraction folder} has been specified, it is returned.
* <li>If the user can write to the working folder, then it * <li>If the user can write to the working folder, then it is
* is returned.</li> * returned.</li>
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} * <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} is
* is used, to prevent collisions, a special subfolder is used * used, to prevent collisions, a special subfolder is used called
* called <code>natives_&lt;hash&gt;</code> where &lt;hash&gt; * <code>natives_&lt;hash&gt;</code> where &lt;hash&gt; is computed
* is computed automatically as the XOR of the classpath hash code * automatically as the XOR of the classpath hash code and the last modified
* and the last modified date of this class. * date of this class.
* *
* @return Path where natives will be extracted to. * @return Path where natives will be extracted to.
*/ */
public static File getExtractionFolder() { public static File getExtractionFolder() {
@ -268,27 +274,27 @@ public final class NativeLibraryLoader {
} }
return extractionFolder; return extractionFolder;
} }
/** /**
* Determine jME3's cache folder for the user account based on the OS. * Determine jME3's cache folder for the user account based on the OS.
* *
* If the OS cache folder is missing, the assumption is that this * If the OS cache folder is missing, the assumption is that this particular
* particular version of the OS does not have a dedicated cache folder, * version of the OS does not have a dedicated cache folder, hence, we use
* hence, we use the user's home folder instead as the root. * the user's home folder instead as the root.
* *
* The folder returned is as follows:<br> * The folder returned is as follows:<br>
* <ul> * <ul>
* <li>Windows: ~\AppData\Local\jme3</li> * <li>Windows: ~\AppData\Local\jme3</li>
* <li>Mac OS X: ~/Library/Caches/jme3</li> * <li>Mac OS X: ~/Library/Caches/jme3</li>
* <li>Linux: ~/.cache/jme3</li> * <li>Linux: ~/.cache/jme3</li>
* </ul> * </ul>
* *
* @return the user cache folder. * @return the user cache folder.
*/ */
private static File getJmeUserCacheFolder() { private static File getJmeUserCacheFolder() {
File userHomeFolder = new File(System.getProperty("user.home")); File userHomeFolder = new File(System.getProperty("user.home"));
File userCacheFolder = null; File userCacheFolder = null;
switch (JmeSystem.getPlatform()) { switch (JmeSystem.getPlatform()) {
case Linux32: case Linux32:
case Linux64: case Linux64:
@ -305,31 +311,31 @@ public final class NativeLibraryLoader {
userCacheFolder = new File(new File(userHomeFolder, "AppData"), "Local"); userCacheFolder = new File(new File(userHomeFolder, "AppData"), "Local");
break; break;
} }
if (userCacheFolder == null || !userCacheFolder.exists()) { if (userCacheFolder == null || !userCacheFolder.exists()) {
// Fallback to home directory if cache folder is missing // Fallback to home directory if cache folder is missing
return new File(userHomeFolder, ".jme3"); return new File(userHomeFolder, ".jme3");
} }
return new File(userCacheFolder, "jme3"); return new File(userCacheFolder, "jme3");
} }
private static void setExtractionFolderToUserCache() { private static void setExtractionFolderToUserCache() {
File extractFolderInHome = getJmeUserCacheFolder(); File extractFolderInHome = getJmeUserCacheFolder();
if (!extractFolderInHome.exists()) { if (!extractFolderInHome.exists()) {
extractFolderInHome.mkdir(); extractFolderInHome.mkdir();
} }
extractionFolder = new File(extractFolderInHome, "natives_" + Integer.toHexString(computeNativesHash())); extractionFolder = new File(extractFolderInHome, "natives_" + Integer.toHexString(computeNativesHash()));
if (!extractionFolder.exists()) { if (!extractionFolder.exists()) {
extractionFolder.mkdir(); extractionFolder.mkdir();
} }
logger.log(Level.WARNING, "Working directory is not writable. " logger.log(Level.WARNING, "Working directory is not writable. "
+ "Natives will be extracted to:\n{0}", + "Natives will be extracted to:\n{0}",
extractionFolder); extractionFolder);
} }
private static int computeNativesHash() { private static int computeNativesHash() {
@ -360,11 +366,12 @@ public final class NativeLibraryLoader {
try { try {
conn.getInputStream().close(); conn.getInputStream().close();
conn.getOutputStream().close(); conn.getOutputStream().close();
} catch (IOException ex) { } } catch (IOException ex) {
}
} }
} }
} }
public static File[] getJarsWithNatives() { public static File[] getJarsWithNatives() {
HashSet<File> jarFiles = new HashSet<File>(); HashSet<File> jarFiles = new HashSet<File>();
for (Map.Entry<NativeLibrary.Key, NativeLibrary> lib : nativeLibraryMap.entrySet()) { for (Map.Entry<NativeLibrary.Key, NativeLibrary> lib : nativeLibraryMap.entrySet()) {
@ -375,7 +382,7 @@ public final class NativeLibraryLoader {
} }
return jarFiles.toArray(new File[0]); return jarFiles.toArray(new File[0]);
} }
public static void extractNativeLibraries(Platform platform, File targetDir) throws IOException { public static void extractNativeLibraries(Platform platform, File targetDir) throws IOException {
for (Map.Entry<NativeLibrary.Key, NativeLibrary> lib : nativeLibraryMap.entrySet()) { for (Map.Entry<NativeLibrary.Key, NativeLibrary> lib : nativeLibraryMap.entrySet()) {
if (lib.getValue().getPlatform() == platform) { if (lib.getValue().getPlatform() == platform) {
@ -386,7 +393,7 @@ public final class NativeLibraryLoader {
} }
} }
} }
private static String mapLibraryName_emulated(String name, Platform platform) { private static String mapLibraryName_emulated(String name, Platform platform) {
switch (platform) { switch (platform) {
case MacOSX32: case MacOSX32:
@ -399,10 +406,10 @@ public final class NativeLibraryLoader {
return name + ".so"; return name + ".so";
} }
} }
/** /**
* Removes platform-specific portions of a library file name so * Removes platform-specific portions of a library file name so that it can
* that it can be accepted by {@link System#loadLibrary(java.lang.String) }. * be accepted by {@link System#loadLibrary(java.lang.String) }.
* <p> * <p>
* E.g.<br> * E.g.<br>
* <ul> * <ul>
@ -410,7 +417,7 @@ public final class NativeLibraryLoader {
* <li>liblwjgl64.so => lwjgl64</li> * <li>liblwjgl64.so => lwjgl64</li>
* <li>libopenal.so => openal</li> * <li>libopenal.so => openal</li>
* </ul> * </ul>
* *
* @param filename The filename to strip platform-specific parts * @param filename The filename to strip platform-specific parts
* @return The stripped library name * @return The stripped library name
*/ */
@ -425,7 +432,7 @@ public final class NativeLibraryLoader {
} }
return sb.toString(); return sb.toString();
} }
public static File getJarForNativeLibrary(Platform platform, String name) { public static File getJarForNativeLibrary(Platform platform, String name) {
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform)); NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
if (library == null) { if (library == null) {
@ -436,23 +443,23 @@ public final class NativeLibraryLoader {
if (pathInJar == null) { if (pathInJar == null) {
return null; return null;
} }
String fileNameInJar; String fileNameInJar;
if (pathInJar.contains("/")) { if (pathInJar.contains("/")) {
fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1); fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1);
} else { } else {
fileNameInJar = pathInJar; fileNameInJar = pathInJar;
} }
URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
if (url == null) { if (url == null) {
url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar); url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
} }
if (url == null) { if (url == null) {
return null; return null;
} }
StringBuilder sb = new StringBuilder(url.toString()); StringBuilder sb = new StringBuilder(url.toString());
if (sb.indexOf("jar:file:/") == 0) { if (sb.indexOf("jar:file:/") == 0) {
sb.delete(0, 9); sb.delete(0, 9);
@ -462,7 +469,7 @@ public final class NativeLibraryLoader {
return null; // not a jar return null; // not a jar
} }
} }
public static void extractNativeLibrary(Platform platform, String name, File targetDir) throws IOException { public static void extractNativeLibrary(Platform platform, String name, File targetDir) throws IOException {
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform)); NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
if (library == null) { if (library == null) {
@ -473,19 +480,19 @@ public final class NativeLibraryLoader {
if (pathInJar == null) { if (pathInJar == null) {
return; return;
} }
String fileNameInJar; String fileNameInJar;
if (pathInJar.contains("/")) { if (pathInJar.contains("/")) {
fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1); fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1);
} else { } else {
fileNameInJar = pathInJar; fileNameInJar = pathInJar;
} }
URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
if (url == null) { if (url == null) {
url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar); url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
} }
if (url == null) { if (url == null) {
return; return;
} }
@ -496,10 +503,10 @@ public final class NativeLibraryLoader {
} else { } else {
loadedAsFileName = fileNameInJar; loadedAsFileName = fileNameInJar;
} }
URLConnection conn = url.openConnection(); URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream(); InputStream in = conn.getInputStream();
File targetFile = new File(targetDir, loadedAsFileName); File targetFile = new File(targetDir, loadedAsFileName);
OutputStream out = null; OutputStream out = null;
try { try {
@ -526,12 +533,26 @@ public final class NativeLibraryLoader {
/** /**
* First extracts the native library and then loads it. * First extracts the native library and then loads it.
* *
* @param name The name of the library to load. * @param name The name of the library to load.
* @param isRequired If true and the library fails to load, throw exception. If * @param isRequired If true and the library fails to load, throw exception.
* false, do nothing if it fails to load. * If false, do nothing if it fails to load.
*/ */
public static void loadNativeLibrary(String name, boolean isRequired) { public static void loadNativeLibrary(String name, boolean isRequired) {
loadNativeLibrary(name, isRequired, true);
}
/**
* First extracts the native library and then (optionally) loads it.
*
* @param name The name of the library to load.
* @param isRequired If true and the library fails to load, throw exception.
* If false, do nothing if it fails to load.
* @param loadLibrary If true, call
* {@link System#loadLibrary(java.lang.String)} on the library, otherwise,
* do nothing after extraction.
*/
public static void loadNativeLibrary(String name, boolean isRequired, boolean loadLibrary) {
if (JmeSystem.isLowPermissions()) { if (JmeSystem.isLowPermissions()) {
throw new UnsupportedOperationException("JVM is running under " throw new UnsupportedOperationException("JVM is running under "
+ "reduced permissions. Cannot load native libraries."); + "reduced permissions. Cannot load native libraries.");
@ -539,7 +560,7 @@ public final class NativeLibraryLoader {
Platform platform = JmeSystem.getPlatform(); Platform platform = JmeSystem.getPlatform();
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform)); NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
if (library == null) { if (library == null) {
// No library exists for this platform. // No library exists for this platform.
if (isRequired) { if (isRequired) {
@ -547,20 +568,20 @@ public final class NativeLibraryLoader {
"The required native library '" + name + "'" "The required native library '" + name + "'"
+ " is not available for your OS: " + platform); + " is not available for your OS: " + platform);
} else { } else {
logger.log(Level.FINE, "The optional native library ''{0}''" + logger.log(Level.FINE, "The optional native library ''{0}''"
" is not available for your OS: {1}", + " is not available for your OS: {1}",
new Object[]{name, platform}); new Object[]{name, platform});
return; return;
} }
} }
final String pathInJar = library.getPathInNativesJar(); final String pathInJar = library.getPathInNativesJar();
if (pathInJar == null) { if (pathInJar == null) {
// This platform does not require the native library to be loaded. // This platform does not require the native library to be loaded.
return; return;
} }
final String fileNameInJar; final String fileNameInJar;
if (pathInJar.contains("/")) { if (pathInJar.contains("/")) {
@ -570,7 +591,7 @@ public final class NativeLibraryLoader {
} }
URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
if (url == null) { if (url == null) {
// Try the root of the classpath as well. // Try the root of the classpath as well.
url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar); url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
@ -578,15 +599,15 @@ public final class NativeLibraryLoader {
if (url == null) { if (url == null) {
// Attempt to load it as a system library. // Attempt to load it as a system library.
// Need to unmap it from library specific parts.
String unmappedName = unmapLibraryName(fileNameInJar); String unmappedName = unmapLibraryName(fileNameInJar);
try { try {
// XXX: HACK. Vary loading method based on library name.. if (loadLibrary) {
// lwjgl and jinput handle loading by themselves.
if (!name.equals("lwjgl") && !name.equals("jinput")) {
// Need to unmap it from library specific parts.
System.loadLibrary(unmappedName); System.loadLibrary(unmappedName);
logger.log(Level.FINE, "Loaded system installed " logger.log(Level.FINE, "Loaded system installed "
+ "version of native library: {0}", unmappedName); + "version of native library: {0}", unmappedName);
} else {
throw new UnsatisfiedLinkError();
} }
} catch (UnsatisfiedLinkError e) { } catch (UnsatisfiedLinkError e) {
if (isRequired) { if (isRequired) {
@ -595,16 +616,16 @@ public final class NativeLibraryLoader {
+ " was not found in the classpath via '" + pathInJar + " was not found in the classpath via '" + pathInJar
+ "'. Error message: " + e.getMessage()); + "'. Error message: " + e.getMessage());
} else { } else {
logger.log(Level.FINE, "The optional native library ''{0}''" + logger.log(Level.FINE, "The optional native library ''{0}''"
" was not found in the classpath via ''{1}''" + + " was not found in the classpath via ''{1}''"
". Error message: {2}", + ". Error message: {2}",
new Object[]{unmappedName, pathInJar, e.getMessage()}); new Object[]{unmappedName, pathInJar, e.getMessage()});
} }
} }
return; return;
} }
// The library has been found and is ready to be extracted. // The library has been found and is ready to be extracted.
// Determine what filename it should be extracted as. // Determine what filename it should be extracted as.
String loadedAsFileName; String loadedAsFileName;
@ -614,53 +635,57 @@ public final class NativeLibraryLoader {
// Just use the original filename as it is in the JAR. // Just use the original filename as it is in the JAR.
loadedAsFileName = fileNameInJar; loadedAsFileName = fileNameInJar;
} }
File extactionDirectory = getExtractionFolder(); File extactionDirectory = getExtractionFolder();
URLConnection conn; URLConnection conn;
InputStream in; InputStream in;
try { try {
conn = url.openConnection(); conn = url.openConnection();
in = conn.getInputStream(); in = conn.getInputStream();
} catch (IOException ex) { } catch (IOException ex) {
// Maybe put more detail here? Not sure.. // Maybe put more detail here? Not sure..
throw new UnsatisfiedLinkError("Failed to open file: '" + url + throw new UnsatisfiedLinkError("Failed to open file: '" + url
"'. Error: " + ex); + "'. Error: " + ex);
} }
File targetFile = new File(extactionDirectory, loadedAsFileName); File targetFile = new File(extactionDirectory, loadedAsFileName);
OutputStream out = null; FileOutputStream out = null;
try { try {
if (targetFile.exists()) { if (targetFile.exists()) {
// OK, compare last modified date of this file to // OK, compare last modified date of this file to
// file in jar // file in jar
long targetLastModified = targetFile.lastModified(); long targetLastModified = targetFile.lastModified();
long sourceLastModified = conn.getLastModified(); long sourceLastModified = conn.getLastModified();
// Allow ~1 second range for OSes that only support low precision // Allow ~1 second range for OSes that only support low precision
if (targetLastModified + 1000 > sourceLastModified) { if (Math.abs(targetLastModified - sourceLastModified) < 1000) {
logger.log(Level.FINE, "Not copying library {0}. " + logger.log(Level.FINE, "Not copying library {0}. "
"Latest already extracted.", + "Identical version already extracted.",
loadedAsFileName); loadedAsFileName);
return; return;
} }
} }
out = new FileOutputStream(targetFile); out = new FileOutputStream(targetFile);
FileLock lock = out.getChannel().lock();
int len; int len;
while ((len = in.read(buf)) > 0) { while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len); out.write(buf, 0, len);
} }
in.close(); in.close();
in = null; in = null;
out.close(); out.close();
out = null; out = null;
// NOTE: On OSes that support "Date Created" property, // NOTE: On OSes that support "Date Created" property,
// this will cause the last modified date to be lower than // this will cause the last modified date to be lower than
// date created which makes no sense // date created which makes no sense
targetFile.setLastModified(conn.getLastModified()); targetFile.setLastModified(conn.getLastModified());
} catch (OverlappingFileLockException ex) {
// do nothing with ex
} catch (IOException ex) { } catch (IOException ex) {
if (ex.getMessage().contains("used by another process")) { if (ex.getMessage().contains("used by another process")) {
return; return;
@ -669,30 +694,26 @@ public final class NativeLibraryLoader {
+ "library to: " + targetFile); + "library to: " + targetFile);
} }
} finally { } finally {
// XXX: HACK. Vary loading method based on library name.. if (loadLibrary) {
// lwjgl and jinput handle loading by themselves.
if (name.equals("lwjgl") || name.equals("lwjgl3")) {
System.setProperty("org.lwjgl.librarypath",
extactionDirectory.getAbsolutePath());
} else if (name.equals("jinput")) {
System.setProperty("net.java.games.input.librarypath",
extactionDirectory.getAbsolutePath());
} else {
// all other libraries (openal, bulletjme, custom)
// will load directly in here.
System.load(targetFile.getAbsolutePath()); System.load(targetFile.getAbsolutePath());
} }
if(in != null){ if (in != null) {
try { in.close(); } catch (IOException ex) { } try {
in.close();
} catch (IOException ex) {
}
} }
if(out != null){ if (out != null) {
try { out.close(); } catch (IOException ex) { } try {
out.close();
} catch (IOException ex) {
}
} }
} }
logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''", logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''",
new Object[]{url, targetFile}); new Object[]{url, targetFile});
} }
} }

@ -34,6 +34,7 @@ package com.jme3.system.awt;
import com.jme3.post.SceneProcessor; import com.jme3.post.SceneProcessor;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer; import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
@ -47,20 +48,19 @@ import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy; import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class AwtPanel extends Canvas implements SceneProcessor { public class AwtPanel extends Canvas implements JmePanel, SceneProcessor {
private boolean attachAsMain = false; private boolean attachAsMain = false;
private BufferedImage img; private BufferedImage img;
private FrameBuffer fb; // private FrameBuffer fb;
private boolean srgb = false;
private ByteBuffer byteBuf;
private IntBuffer intBuf;
private RenderManager rm; private RenderManager rm;
private PaintMode paintMode; private PaintMode paintMode;
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>(); private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
@ -75,35 +75,47 @@ public class AwtPanel extends Canvas implements SceneProcessor {
// Reshape vars // Reshape vars
private int newWidth = 1; private int newWidth = 1;
private int newHeight = 1; private int newHeight = 1;
private AtomicBoolean reshapeNeeded = new AtomicBoolean(false); private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
private final Object lock = new Object(); private final Object lock = new Object();
public AwtPanel(PaintMode paintMode){ // Buffer pool and pending buffers
this(paintMode, false); private int NUM_FRAMES = 3;
} private final ArrayBlockingQueue<Future<ByteBuffer>> pendingFrames = new ArrayBlockingQueue<Future<ByteBuffer>>(NUM_FRAMES);
private final ArrayBlockingQueue<ByteBuffer> bufferPool = new ArrayBlockingQueue<ByteBuffer>(NUM_FRAMES);
private final ArrayList<FrameBuffer> fbs = new ArrayList<FrameBuffer>(NUM_FRAMES);
private int frameIndex = 0;
private final ComponentAdapter resizeListener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
onResize(e);
}
};
public AwtPanel(PaintMode paintMode, boolean srgb){ public AwtPanel(PaintMode paintMode, boolean srgb){
this.paintMode = paintMode; this.paintMode = paintMode;
this.srgb = srgb;
invalidatePendingFrames();
if (paintMode == PaintMode.Accelerated){ if (paintMode == PaintMode.Accelerated){
setIgnoreRepaint(true); setIgnoreRepaint(true);
} }
addComponentListener(new ComponentAdapter(){ addComponentListener(resizeListener);
@Override }
public void componentResized(ComponentEvent e) {
synchronized (lock){ public void onResize(ComponentEvent e) {
int newWidth2 = Math.max(getWidth(), 1); synchronized (lock) {
int newHeight2 = Math.max(getHeight(), 1); int newWidth2 = Math.max(getWidth(), 1);
if (newWidth != newWidth2 || newHeight != newHeight2){ int newHeight2 = Math.max(getHeight(), 1);
newWidth = newWidth2; if (newWidth != newWidth2 || newHeight != newHeight2) {
newHeight = newHeight2; newWidth = newWidth2;
reshapeNeeded.set(true); newHeight = newHeight2;
System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); reshapeNeeded.set(true);
} System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
}
} }
}); }
} }
@Override @Override
@ -128,13 +140,13 @@ public class AwtPanel extends Canvas implements SceneProcessor {
super.removeNotify(); super.removeNotify();
} }
@Override // @Override
public void paint(Graphics g){ // public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g; // Graphics2D g2d = (Graphics2D) g;
synchronized (lock){ // synchronized (lock){
g2d.drawImage(img, transformOp, 0, 0); // g2d.drawImage(img, transformOp, 0, 0);
} // }
} // }
public boolean checkVisibilityState(){ public boolean checkVisibilityState(){
if (!hasNativePeer.get()){ if (!hasNativePeer.get()){
@ -157,24 +169,85 @@ public class AwtPanel extends Canvas implements SceneProcessor {
return currentShowing; return currentShowing;
} }
public void repaintInThread(){ // public void repaintInThread(){
// Convert screenshot. // // Convert screenshot.
byteBuf.clear(); // byteBuf.clear();
rm.getRenderer().readFrameBuffer(fb, byteBuf); // rm.getRenderer().readFrameBuffer(fb, byteBuf);
//
// synchronized (lock){
// // All operations on img must be synchronized
// // as it is accessed from EDT.
// Screenshots.convertScreenShot2(intBuf, img);
// repaint();
// }
// }
public ByteBuffer acquireNextFrame() {
if (pendingFrames.isEmpty()) {
System.out.println("!!! No pending frames, returning null.");
return null;
}
synchronized (lock){ try {
// All operations on img must be synchronized ByteBuffer nextFrame = null;
// as it is accessed from EDT.
Screenshots.convertScreenShot2(intBuf, img); // while (!pendingFrames.isEmpty() && pendingFrames.peek().isDone()) {
repaint(); // nextFrame = pendingFrames.take().get();
// }
//
// if (nextFrame != null) {
// return nextFrame;
// }
//
// if (pendingFrames.remainingCapacity() == 0) {
// Force it to finish ..
return pendingFrames.take().get();
// }
// Some frames are pending, none are finished though.
// return null;
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
} }
} }
public void drawFrameInThread(){ public void readNextFrame() {
// Convert screenshot. if (bufferPool.isEmpty()) {
byteBuf.clear(); System.out.println("??? Too many pending frames!");
rm.getRenderer().readFrameBuffer(fb, byteBuf); return; // need to draw more frames ..
Screenshots.convertScreenShot2(intBuf, img); }
try {
int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4;
ByteBuffer byteBuf = bufferPool.take();
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size);
byteBuf.clear();
GLRenderer renderer = (GLRenderer) rm.getRenderer();
Future<ByteBuffer> future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf);
if (!pendingFrames.offer(future)) {
throw new AssertionError();
}
frameIndex ++;
if (frameIndex >= NUM_FRAMES) {
frameIndex = 0;
}
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
public void drawFrameInThread(ByteBuffer byteBuf){
// Convert the frame into the image so it can be rendered.
Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img);
// return the frame back to its rightful owner.
if (!bufferPool.offer(byteBuf)) {
throw new AssertionError();
}
synchronized (lock){ synchronized (lock){
// All operations on strategy should be synchronized (?) // All operations on strategy should be synchronized (?)
@ -235,44 +308,65 @@ public class AwtPanel extends Canvas implements SceneProcessor {
if (this.rm == null){ if (this.rm == null){
// First time called in OGL thread // First time called in OGL thread
this.rm = rm; this.rm = rm;
reshapeInThread(1, 1); // reshapeInThread(1, 1);
}
}
private void updateAccelerated() {
readNextFrame();
ByteBuffer byteBuf = acquireNextFrame();
if (byteBuf != null) {
drawFrameInThread(byteBuf);
}
}
private void invalidatePendingFrames() {
// NOTE: all pending read requests are invalid!
for (Future<ByteBuffer> pendingRequest : pendingFrames) {
pendingRequest.cancel(true);
}
pendingFrames.clear();
bufferPool.clear();
// Populate buffer pool.
int cap = bufferPool.remainingCapacity();
for (int i = 0; i < cap; i++) {
bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4));
} }
} }
private void reshapeInThread(int width, int height) { private void reshapeInThread(int width, int height) {
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4); invalidatePendingFrames();
intBuf = byteBuf.asIntBuffer();
for (FrameBuffer fb : fbs) {
if (fb != null) {
fb.dispose(); fb.dispose();
fb = null;
} }
fbs.clear();
fb = new FrameBuffer(width, height, 1); for (int i = 0; i < NUM_FRAMES; i++) {
fb.setDepthBuffer(Format.Depth); FrameBuffer fb = new FrameBuffer(width, height, 1);
fb.setColorBuffer(Format.RGB8); fb.setDepthBuffer(Format.Depth);
fb.setSrgb(srgb); fb.setColorBuffer(Format.RGBA8);
fbs.add(fb);
if (attachAsMain){
rm.getRenderer().setMainFrameBufferOverride(fb);
} }
// if (attachAsMain){
// rm.getRenderer().setMainFrameBufferOverride(fb);
// }
synchronized (lock){ synchronized (lock){
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
} }
// synchronized (lock){
// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
// }
AffineTransform tx = AffineTransform.getScaleInstance(1, -1); AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -img.getHeight()); tx.translate(0, -img.getHeight());
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
for (ViewPort vp : viewPorts){ for (ViewPort vp : viewPorts){
if (!attachAsMain){ // if (!attachAsMain){
vp.setOutputFrameBuffer(fb); // vp.setOutputFrameBuffer(fb);
} // }
vp.getCamera().resize(width, height, true); vp.getCamera().resize(width, height, true);
// NOTE: Hack alert. This is done ONLY for custom framebuffers. // NOTE: Hack alert. This is done ONLY for custom framebuffers.
@ -283,8 +377,9 @@ public class AwtPanel extends Canvas implements SceneProcessor {
} }
} }
@Override
public boolean isInitialized() { public boolean isInitialized() {
return fb != null; return rm != null;
} }
public void preFrame(float tpf) { public void preFrame(float tpf) {
@ -298,8 +393,21 @@ public class AwtPanel extends Canvas implements SceneProcessor {
// For "PaintMode.OnDemand" only. // For "PaintMode.OnDemand" only.
repaintRequest.set(true); repaintRequest.set(true);
} }
@Override
public Component getComponent() {
return this;
}
@Override
public void onFrameBegin() {
if (attachAsMain && rm != null){
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex));
}
}
void onFrameEnd() { @Override
public void onFrameEnd() {
if (reshapeNeeded.getAndSet(false)) { if (reshapeNeeded.getAndSet(false)) {
reshapeInThread(newWidth, newHeight); reshapeInThread(newWidth, newHeight);
} else { } else {
@ -309,26 +417,24 @@ public class AwtPanel extends Canvas implements SceneProcessor {
switch (paintMode) { switch (paintMode) {
case Accelerated: case Accelerated:
drawFrameInThread(); updateAccelerated();
break;
case Repaint:
repaintInThread();
break;
case OnRequest:
if (repaintRequest.getAndSet(false)) {
repaintInThread();
}
break; break;
// case Repaint:
// repaintInThread();
// break;
// case OnRequest:
// if (repaintRequest.getAndSet(false)) {
// repaintInThread();
// }
// break;
} }
} }
} }
public void postFrame(FrameBuffer out) { public void postFrame(FrameBuffer out) {
if (!attachAsMain && out != fb){ // if (!attachAsMain && out != fb){
throw new IllegalStateException("Why did you change the output framebuffer?"); // throw new IllegalStateException("Why did you change the output framebuffer?");
} // }
// onFrameEnd();
} }
public void reshape(ViewPort vp, int w, int h) { public void reshape(ViewPort vp, int w, int h) {

@ -46,8 +46,8 @@ public class AwtPanelsContext implements JmeContext {
protected JmeContext actualContext; protected JmeContext actualContext;
protected AppSettings settings = new AppSettings(true); protected AppSettings settings = new AppSettings(true);
protected SystemListener listener; protected SystemListener listener;
protected ArrayList<AwtPanel> panels = new ArrayList<AwtPanel>(); protected ArrayList<JmePanel> panels = new ArrayList<JmePanel>();
protected AwtPanel inputSource; protected JmePanel inputSource;
protected AwtMouseInput mouseInput = new AwtMouseInput(); protected AwtMouseInput mouseInput = new AwtMouseInput();
protected AwtKeyInput keyInput = new AwtKeyInput(); protected AwtKeyInput keyInput = new AwtKeyInput();
@ -92,13 +92,13 @@ public class AwtPanelsContext implements JmeContext {
} }
} }
public void setInputSource(AwtPanel panel){ public void setInputSource(JmePanel panel){
if (!panels.contains(panel)) if (!panels.contains(panel))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
inputSource = panel; inputSource = panel;
mouseInput.setInputSource(panel); mouseInput.setInputSource(panel.getComponent());
keyInput.setInputSource(panel); keyInput.setInputSource(panel.getComponent());
} }
public Type getType() { public Type getType() {
@ -148,14 +148,14 @@ public class AwtPanelsContext implements JmeContext {
public AwtPanelsContext(){ public AwtPanelsContext(){
} }
public AwtPanel createPanel(PaintMode paintMode){ public JmePanel createPanel(PaintMode paintMode){
AwtPanel panel = new AwtPanel(paintMode); JmePanel panel = new SwingPanel(paintMode, true);
panels.add(panel); panels.add(panel);
return panel; return panel;
} }
public AwtPanel createPanel(PaintMode paintMode, boolean srgb){ public JmePanel createPanel(PaintMode paintMode, boolean srgb){
AwtPanel panel = new AwtPanel(paintMode, srgb); JmePanel panel = new SwingPanel(paintMode, srgb);
panels.add(panel); panels.add(panel);
return panel; return panel;
} }
@ -168,18 +168,18 @@ public class AwtPanelsContext implements JmeContext {
// Check if throttle required // Check if throttle required
boolean needThrottle = true; boolean needThrottle = true;
for (AwtPanel panel : panels){ for (JmePanel panel : panels){
if (panel.isActiveDrawing()){ if (panel.isActiveDrawing()){
needThrottle = false; needThrottle = false;
break; break;
} }
} }
if (lastThrottleState != needThrottle){ if (lastThrottleState != needThrottle) {
lastThrottleState = needThrottle; lastThrottleState = needThrottle;
if (lastThrottleState){ if (lastThrottleState) {
System.out.println("OGL: Throttling update loop."); System.out.println("OGL: Throttling update loop.");
}else{ } else {
System.out.println("OGL: Ceased throttling update loop."); System.out.println("OGL: Ceased throttling update loop.");
} }
} }
@ -191,9 +191,13 @@ public class AwtPanelsContext implements JmeContext {
} }
} }
for (JmePanel panel : panels){
panel.onFrameBegin();
}
listener.update(); listener.update();
for (AwtPanel panel : panels){ for (JmePanel panel : panels){
panel.onFrameEnd(); panel.onFrameEnd();
} }
} }

@ -0,0 +1,48 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.system.awt;
import com.jme3.renderer.ViewPort;
import java.awt.Component;
public interface JmePanel {
public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps);
public boolean isActiveDrawing();
public void onFrameBegin();
public void onFrameEnd();
public Component getComponent();
}

@ -0,0 +1,382 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.system.awt;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.util.BufferUtils;
import com.jme3.util.Screenshots;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Canvas;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.ImageCapabilities;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
public class SwingPanel extends JPanel implements JmePanel, SceneProcessor {
private boolean attachAsMain = false;
private BufferedImage img;
// private FrameBuffer fb;
private RenderManager rm;
private PaintMode paintMode;
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
// Visibility/drawing vars
private AffineTransformOp transformOp;
private AtomicBoolean hasNativePeer = new AtomicBoolean(false);
private AtomicBoolean showing = new AtomicBoolean(false);
private AtomicBoolean repaintRequest = new AtomicBoolean(false);
// Reshape vars
private int newWidth = 1;
private int newHeight = 1;
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
private final Object lock = new Object();
// Buffer pool and pending buffers
private final int NUM_FRAMES = 2;
private final ArrayBlockingQueue<Future<ByteBuffer>> pendingFrames = new ArrayBlockingQueue<Future<ByteBuffer>>(NUM_FRAMES);
private final ArrayBlockingQueue<ByteBuffer> bufferPool = new ArrayBlockingQueue<ByteBuffer>(NUM_FRAMES);
private final ArrayList<FrameBuffer> fbs = new ArrayList<FrameBuffer>(NUM_FRAMES);
private int frameIndex = 0;
private final ComponentAdapter resizeListener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
onResize(e);
}
};
public SwingPanel(PaintMode paintMode, boolean srgb){
this.paintMode = paintMode;
invalidatePendingFrames();
addComponentListener(resizeListener);
}
public void onResize(ComponentEvent e) {
synchronized (lock) {
int newWidth2 = Math.max(getWidth(), 1);
int newHeight2 = Math.max(getHeight(), 1);
if (newWidth != newWidth2 || newHeight != newHeight2) {
newWidth = newWidth2;
newHeight = newHeight2;
reshapeNeeded.set(true);
System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
}
}
}
@Override
public Component getComponent() {
return this;
}
@Override
public void addNotify(){
super.addNotify();
synchronized (lock){
hasNativePeer.set(true);
System.out.println("EDT: addNotify");
}
requestFocusInWindow();
}
@Override
public void removeNotify(){
synchronized (lock){
hasNativePeer.set(false);
System.out.println("EDT: removeNotify");
}
super.removeNotify();
}
public boolean checkVisibilityState() {
if (!hasNativePeer.get()) {
return false;
}
boolean currentShowing = isShowing();
if (showing.getAndSet(currentShowing) != currentShowing) {
if (currentShowing) {
System.out.println("OGL: Enter showing state.");
} else {
System.out.println("OGL: Exit showing state.");
}
}
return currentShowing;
}
@Override
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
ByteBuffer byteBuf = null;
synchronized (lock){
if (pendingFrames.size() > NUM_FRAMES - 1) {
byteBuf = acquireNextFrame();
}
if (byteBuf != null) {
// Convert the frame into the image so it can be rendered.
Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img);
try {
// return the frame back to its rightful owner.
bufferPool.put(byteBuf);
} catch (InterruptedException ex) {
Logger.getLogger(SwingPanel.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
g2d.drawImage(img, transformOp, 0, 0);
}
public ByteBuffer acquireNextFrame() {
if (pendingFrames.isEmpty()) {
System.out.println("!!! No pending frames, returning null.");
return null;
}
try {
return pendingFrames.take().get();
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
/**
* Grabs an available buffer from the available frames pool,
* reads the OpenGL backbuffer into it, then adds it to the pending frames pool.
*/
public void readNextFrame() {
if (bufferPool.isEmpty()) {
System.out.println("??? Too many pending frames!");
return; // need to draw more frames ..
}
try {
int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4;
ByteBuffer byteBuf = bufferPool.take();
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size);
byteBuf.clear();
GLRenderer renderer = (GLRenderer) rm.getRenderer();
// Future<ByteBuffer> future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf);
// if (!pendingFrames.offer(future)) {
// throw new AssertionError();
// }
frameIndex ++;
if (frameIndex >= NUM_FRAMES) {
frameIndex = 0;
}
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
@Override
public boolean isActiveDrawing() {
return paintMode != PaintMode.OnRequest && showing.get();
}
public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){
if (viewPorts.size() > 0){
for (ViewPort vp : viewPorts){
vp.setOutputFrameBuffer(null);
}
viewPorts.get(viewPorts.size()-1).removeProcessor(this);
}
viewPorts.addAll(Arrays.asList(vps));
viewPorts.get(viewPorts.size()-1).addProcessor(this);
this.attachAsMain = overrideMainFramebuffer;
}
public void initialize(RenderManager rm, ViewPort vp) {
if (this.rm == null){
// First time called in OGL thread
this.rm = rm;
// reshapeInThread(1, 1);
}
}
private void invalidatePendingFrames() {
// NOTE: all pending read requests are invalid!
for (Future<ByteBuffer> pendingRequest : pendingFrames) {
pendingRequest.cancel(true);
}
pendingFrames.clear();
bufferPool.clear();
// Populate buffer pool.
int cap = bufferPool.remainingCapacity();
for (int i = 0; i < cap; i++) {
bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4));
}
}
private void reshapeInThread(int width, int height) {
invalidatePendingFrames();
for (FrameBuffer fb : fbs) {
fb.dispose();
}
fbs.clear();
for (int i = 0; i < NUM_FRAMES; i++) {
FrameBuffer fb = new FrameBuffer(width, height, 1);
fb.setDepthBuffer(Image.Format.Depth);
fb.setColorBuffer(Image.Format.RGBA8);
fbs.add(fb);
}
synchronized (lock){
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -img.getHeight());
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
}
if (attachAsMain) {
rm.notifyReshape(width, height);
} else {
for (ViewPort vp : viewPorts){
vp.getCamera().resize(width, height, true);
// NOTE: Hack alert. This is done ONLY for custom framebuffers.
// Main framebuffer should use RenderManager.notifyReshape().
for (SceneProcessor sp : vp.getProcessors()){
sp.reshape(vp, width, height);
}
}
}
}
@Override
public boolean isInitialized() {
return rm != null;
}
@Override
public void preFrame(float tpf) {
}
@Override
public void postQueue(RenderQueue rq) {
}
@Override
public void invalidate(){
// For "PaintMode.OnDemand" only.
repaintRequest.set(true);
}
@Override
public void onFrameBegin() {
if (attachAsMain && rm != null){
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex));
}
}
@Override
public void onFrameEnd() {
if (reshapeNeeded.getAndSet(false)) {
reshapeInThread(newWidth, newHeight);
} else {
if (!checkVisibilityState()) {
return;
}
switch (paintMode) {
case Accelerated:
case Repaint:
readNextFrame();
repaint();
break;
case OnRequest:
if (repaintRequest.getAndSet(false)) {
readNextFrame();
repaint();
}
break;
}
}
}
public void postFrame(FrameBuffer out) {
}
public void reshape(ViewPort vp, int w, int h) {
}
public void cleanup() {
}
}

@ -0,0 +1,203 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.system;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileLock;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.FixMethodOrder;
import org.junit.experimental.categories.Category;
import com.jme3.IntegrationTest;
import org.junit.Ignore;
/**
* Integration test for {@link NativeLibraryLoader}.
*
* Note that it uses the file system.
*
* @author Kirill Vainer
*/
@Ignore
@Category(IntegrationTest.class)
@FixMethodOrder
public class NativeLibraryLoaderIT {
private File extractFolder;
static {
NativeLibraryLoader.registerNativeLibrary("test", Platform.Linux64, "natives/linux64/libtest.so");
NativeLibraryLoader.registerNativeLibrary("notexist", Platform.Linux64, "natives/linux64/libnotexist.so");
NativeLibraryLoader.registerNativeLibrary("nativesfolder", Platform.Linux64, "natives/linux64/libnativesfolder.so");
NativeLibraryLoader.registerNativeLibrary("jarroot", Platform.Linux64, "natives/linux64/libjarroot.so");
NativeLibraryLoader.registerNativeLibrary("nullpath", Platform.Linux64, null);
NativeLibraryLoader.registerNativeLibrary("jawt", Platform.Linux64, "whatever/doesnt/matter/libjawt.so");
NativeLibraryLoader.registerNativeLibrary("asname", Platform.Linux64, "natives/linux64/libasname.so", "other.name");
}
@Before
public void setUp() {
extractFolder = NativeLibraryLoader.getExtractionFolder();
}
@Test(expected = UnsatisfiedLinkError.class)
public void testRequiredNonExistentFile() {
NativeLibraryLoader.loadNativeLibrary("notexist", true, false);
}
@Test
public void testOptionalNonExistentFile() throws Exception {
NativeLibraryLoader.loadNativeLibrary("notexist", false, false);
}
@Test(expected = UnsatisfiedLinkError.class)
public void testRequiredUnregisteredLibrary() {
NativeLibraryLoader.loadNativeLibrary("unregistered", true, false);
}
@Test
public void testOptionalUnregisteredLibrary() {
NativeLibraryLoader.loadNativeLibrary("unregistered", false, false);
}
@Test
public void testLibraryNullPath() {
NativeLibraryLoader.loadNativeLibrary("nullpath", true, false);
NativeLibraryLoader.loadNativeLibrary("nullpath", false, false);
}
private static void fudgeLastModifiedTime(File file) {
// fudge last modified date to force extraction attempt
long yesterdayModifiedtime = file.lastModified() - 24 * 60 * 60 * 1000;
assertTrue(file.setLastModified(yesterdayModifiedtime));
assertTrue(Math.abs(file.lastModified() - yesterdayModifiedtime) < 10000);
}
@Test
public void testDifferentLastModifiedDates() throws IOException {
File libFile = new File(extractFolder, "libtest.so");
assertTrue(libFile.createNewFile());
assertTrue(libFile.exists() && libFile.length() == 0);
fudgeLastModifiedTime(libFile);
NativeLibraryLoader.loadNativeLibrary("test", true, false);
assertTrue(libFile.length() == 12);
assertTrue(libFile.delete());
assertTrue(!libFile.exists());
}
@Test
public void testLibraryInUse() throws IOException {
File libFile = new File(extractFolder, "libtest.so");
NativeLibraryLoader.loadNativeLibrary("test", true, false);
assertTrue(libFile.exists());
fudgeLastModifiedTime(libFile);
FileOutputStream out = null;
try {
out = new FileOutputStream(libFile);
FileLock lock = out.getChannel().lock();
assertTrue(lock.isValid());
NativeLibraryLoader.loadNativeLibrary("test", true, false);
} finally {
if (out != null) {
out.close();
}
}
libFile.delete();
}
@Test
public void testLoadSystemLibrary() {
NativeLibraryLoader.loadNativeLibrary("jawt", true, true);
}
@Test
public void testExtractAsName() {
NativeLibraryLoader.loadNativeLibrary("asname", true, false);
assertTrue(new File(extractFolder, "other.name").exists());
assertTrue(new File(extractFolder, "other.name").delete());
}
@Test
public void testCustomExtractFolder() {
File customExtractFolder = new File(System.getProperty("java.io.tmpdir"), "jme3_test_tmp");
if (!customExtractFolder.exists()) {
assertTrue(customExtractFolder.mkdir());
}
NativeLibraryLoader.setCustomExtractionFolder(customExtractFolder.getAbsolutePath());
NativeLibraryLoader.loadNativeLibrary("test", true, false);
assertTrue(new File(customExtractFolder, "libtest.so").exists());
assertTrue(new File(customExtractFolder, "libtest.so").delete());
assertTrue(!new File(customExtractFolder, "libtest.so").exists());
NativeLibraryLoader.setCustomExtractionFolder(null);
NativeLibraryLoader.loadNativeLibrary("test", true, false);
assertTrue(new File(extractFolder, "libtest.so").exists());
new File(extractFolder, "libtest.so").delete();
customExtractFolder.delete();
}
@Test
public void testExtractFromNativesFolderInJar() {
NativeLibraryLoader.loadNativeLibrary("nativesfolder", true, false);
File libFile = new File(extractFolder, "libnativesfolder.so");
assertTrue(libFile.exists() && libFile.length() == 12);
libFile.delete();
}
@Test
public void testExtractFromJarRoot() {
NativeLibraryLoader.loadNativeLibrary("jarroot", true, false);
File libFile = new File(extractFolder, "libjarroot.so");
assertTrue(libFile.exists() && libFile.length() == 12);
libFile.delete();
}
}

@ -1,88 +0,0 @@
package jme3test.app;
import com.jme3.scene.Mesh;
import com.jme3.system.AppSettings;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.prefs.BackingStoreException;
public class TestCustomAppSettings {
private static final String APPSETTINGS_KEY = "JME_AppSettingsTest";
private static void assertEqual(Object a, Object b) {
if (!a.equals(b)){
throw new AssertionError();
}
}
/**
* Tests preference based AppSettings.
*/
private static void testPreferenceSettings() {
AppSettings settings = new AppSettings(false);
settings.putBoolean("TestBool", true);
settings.putInteger("TestInt", 123);
settings.putString("TestStr", "HelloWorld");
settings.putFloat("TestFloat", 123.567f);
settings.put("TestObj", new Mesh()); // Objects not supported by preferences
try {
settings.save(APPSETTINGS_KEY);
} catch (BackingStoreException ex) {
ex.printStackTrace();
}
AppSettings loadedSettings = new AppSettings(false);
try {
loadedSettings.load(APPSETTINGS_KEY);
} catch (BackingStoreException ex) {
ex.printStackTrace();
}
assertEqual(loadedSettings.getBoolean("TestBool"), true);
assertEqual(loadedSettings.getInteger("TestInt"), 123);
assertEqual(loadedSettings.getString("TestStr"), "HelloWorld");
assertEqual(loadedSettings.get("TestFloat"), 123.567f);
}
/**
* Test Java properties file based AppSettings.
*/
private static void testFileSettings() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
AppSettings settings = new AppSettings(false);
settings.putBoolean("TestBool", true);
settings.putInteger("TestInt", 123);
settings.putString("TestStr", "HelloWorld");
settings.putFloat("TestFloat", 123.567f);
settings.put("TestObj", new Mesh()); // Objects not supported by file settings
try {
settings.save(baos);
} catch (IOException ex) {
ex.printStackTrace();
}
AppSettings loadedSettings = new AppSettings(false);
try {
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
loadedSettings.load(bais);
} catch (IOException ex) {
ex.printStackTrace();
}
assertEqual(loadedSettings.getBoolean("TestBool"), true);
assertEqual(loadedSettings.getInteger("TestInt"), 123);
assertEqual(loadedSettings.getString("TestStr"), "HelloWorld");
assertEqual(loadedSettings.get("TestFloat"), 123.567f);
}
public static void main(String[] args){
testPreferenceSettings();
testFileSettings();
System.out.println("All OK");
}
}

@ -1,112 +0,0 @@
package jme3test.awt;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class TestAwtPanels extends SimpleApplication {
final private static CountDownLatch panelsAreReady = new CountDownLatch(1);
private static TestAwtPanels app;
private static AwtPanel panel, panel2;
private static int panelsClosed = 0;
private static void createWindowForPanel(AwtPanel panel, int location){
JFrame frame = new JFrame("Render Display " + location);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
if (++panelsClosed == 2){
app.stop();
}
}
});
frame.pack();
frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 400);
frame.setVisible(true);
}
public static void main(String[] args){
Logger.getLogger("com.jme3").setLevel(Level.WARNING);
app = new TestAwtPanels();
app.setShowSettings(false);
AppSettings settings = new AppSettings(true);
settings.setCustomRenderer(AwtPanelsContext.class);
settings.setFrameRate(60);
app.setSettings(settings);
app.start();
SwingUtilities.invokeLater(new Runnable(){
public void run(){
/*
* Sleep 2 seconds to ensure there's no race condition.
* The sleep is not required for correctness.
*/
try {
Thread.sleep(2000);
} catch (InterruptedException exception) {
return;
}
final AwtPanelsContext ctx = (AwtPanelsContext) app.getContext();
panel = ctx.createPanel(PaintMode.Accelerated);
panel.setPreferredSize(new Dimension(400, 300));
ctx.setInputSource(panel);
panel2 = ctx.createPanel(PaintMode.Accelerated);
panel2.setPreferredSize(new Dimension(400, 300));
createWindowForPanel(panel, 300);
createWindowForPanel(panel2, 700);
/*
* Both panels are ready.
*/
panelsAreReady.countDown();
}
});
}
@Override
public void simpleInitApp() {
flyCam.setDragToRotate(true);
Box b = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry geom = new Geometry("Box", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
geom.setMaterial(mat);
rootNode.attachChild(geom);
/*
* Wait until both AWT panels are ready.
*/
try {
panelsAreReady.await();
} catch (InterruptedException exception) {
throw new RuntimeException("Interrupted while waiting for panels", exception);
}
panel.attachTo(true, viewPort);
guiViewPort.setClearFlags(true, true, true);
panel2.attachTo(false, guiViewPort);
}
}

@ -0,0 +1,141 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test.stress;
import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
// Let's see if we can render 2500 batches in 60 fps.
// We'll use 50 materials with various combinations of textures and colors
// to make things wild.
public class TestUniqueGeometries extends SimpleApplication {
private Material[] randomMaterials = new Material[50];
private String[] textureList = new String[] {
"Blender/2.4x/textures/Concrete_Wall.PNG",
"Blender/2.4x/textures/Grass_256.png",
"Blender/2.4x/textures/SandDesert_StartTower.png",
"Blender/2.4x/textures/Tar_Cracked.png",
"Blender/2.4x/textures/WarningStrip.png",
"Blender/2.4x/WoodCrate_lighter.png",
"Interface/Logo/Monkey.jpg",
"Interface/Logo/Monkey.png",
"Models/Boat/boat.png",
"Models/Ninja/Ninja.jpg",
"Models/Tree/BarkColor.jpg",
"Textures/Terrain/BrickWall/BrickWall.jpg",
"Textures/Terrain/Pond/Pond.jpg",
"Textures/Terrain/Pond/Pond_normal.png",
"Textures/Terrain/Rock/Rock.PNG",
"Textures/Terrain/Rock/Rock_normal.png",
"Textures/Terrain/Rock2/rock.jpg",
"Textures/Terrain/Rocky/RockyNormals.jpg",
"Textures/Terrain/Rocky/RockyTexture.jpg",
"Textures/Terrain/splat/alpha1.png",
"Textures/Terrain/splat/alpha2.png",
"Textures/Terrain/splat/alphamap.png",
"Textures/Terrain/splat/alphamap2.png",
"Textures/Terrain/splat/dirt.jpg",
"Textures/Terrain/splat/dirt_normal.png",
"Textures/Terrain/splat/fortress512.png",
"Textures/Terrain/splat/grass.jpg",
"Textures/Terrain/splat/grass_normal.jpg",
"Textures/Terrain/splat/mountains128.png",
"Textures/Terrain/splat/road.jpg",
"Textures/Terrain/splat/road_normal.png",
};
public static void main(String[] args) {
TestUniqueGeometries app = new TestUniqueGeometries();
AppSettings settings = new AppSettings(true);
settings.putBoolean("GraphicsTrace", false);
settings.putBoolean("GraphicsTiming", true);
app.setSettings(settings);
app.start();
}
private void loadRandomMaterials() {
for (int i = 0; i < randomMaterials.length; i++) {
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setBoolean("VertexLighting", true);
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Ambient", ColorRGBA.Black);
mat.setColor("Diffuse", ColorRGBA.White);
mat.setColor("Specular", ColorRGBA.White);
mat.setFloat("Shininess", 32);
mat.setTexture("DiffuseMap", assetManager.loadTexture(textureList[i % textureList.length]));
randomMaterials[i] = mat;
}
}
@Override
public void simpleInitApp() {
flyCam.setDragToRotate(true);
cam.setLocation(new Vector3f(22.717342f, 18.366547f, 22.043106f));
cam.setRotation(new Quaternion(-0.11630201f, 0.8794429f, -0.27703872f, -0.36919326f));
DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
rootNode.addLight(dl);
flyCam.setMoveSpeed(5);
loadRandomMaterials();
// Box box = new Box(1,1,1);
for (int y = -25; y < 25; y++) {
for (int x = -25; x < 25; x++) {
Material mat = randomMaterials[0]; // randomMaterials[FastMath.nextRandomInt(0, randomMaterials.length - 1)];
Box box = new Box(1,1,1);
Geometry boxClone = new Geometry("box", box);
boxClone.setMaterial(mat);
boxClone.setLocalTranslation(x * .5f, 0, y * .5f);
boxClone.setLocalScale(.15f);
boxClone.setMaterial(mat);
rootNode.attachChild(boxClone);
}
}
}
}

@ -0,0 +1,12 @@
if (!hasProperty('mainClass')) {
ext.mainClass = ''
}
dependencies {
compile project(':jme3-core')
compile project(':jme3-desktop')
compile 'net.java.jinput:jinput:2.0.6'
compile 'net.java.jinput:jinput-platform:2.0.6'
testCompile project(path: ':jme3-core', configuration: 'testOutput')
}

@ -1,4 +1,4 @@
package com.jme3.input.lwjgl; package com.jme3.input.jinput;
import com.jme3.input.AbstractJoystick; import com.jme3.input.AbstractJoystick;
import com.jme3.input.DefaultJoystickAxis; import com.jme3.input.DefaultJoystickAxis;
@ -12,6 +12,7 @@ import com.jme3.input.JoystickCompatibilityMappings;
import com.jme3.input.RawInputListener; import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent; import com.jme3.input.event.JoyButtonEvent;
import com.jme3.system.NativeLibraryLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -32,25 +33,13 @@ public class JInputJoyInput implements JoyInput {
private JInputJoystick[] joysticks; private JInputJoystick[] joysticks;
private RawInputListener listener; private RawInputListener listener;
private Map<Controller, JInputJoystick> joystickIndex = new HashMap<Controller, JInputJoystick>(); private final Map<Controller, JInputJoystick> joystickIndex = new HashMap<Controller, JInputJoystick>();
public void setJoyRumble(int joyId, float amount){
if( joyId >= joysticks.length )
throw new IllegalArgumentException();
Controller c = joysticks[joyId].controller;
for (Rumbler r : c.getRumblers()){
r.rumble(amount);
}
}
@Override
public Joystick[] loadJoysticks(InputManager inputManager){ public Joystick[] loadJoysticks(InputManager inputManager){
ControllerEnvironment ce = ControllerEnvironment ce =
ControllerEnvironment.getDefaultEnvironment(); ControllerEnvironment.getDefaultEnvironment();
Controller[] cs = ce.getControllers();
List<Joystick> list = new ArrayList<Joystick>(); List<Joystick> list = new ArrayList<Joystick>();
for( Controller c : ce.getControllers() ) { for( Controller c : ce.getControllers() ) {
if (c.getType() == Controller.Type.KEYBOARD if (c.getType() == Controller.Type.KEYBOARD
@ -82,10 +71,18 @@ public class JInputJoyInput implements JoyInput {
return joysticks; return joysticks;
} }
@Override
public void initialize() { public void initialize() {
inited = true; inited = true;
// Load natives
String extractPath = NativeLibraryLoader.getExtractionFolder().getAbsolutePath();
System.setProperty("net.java.games.input.librarypath", extractPath);
NativeLibraryLoader.loadNativeLibrary("jinput", true, false);
NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true, false);
} }
@Override
public void update() { public void update() {
ControllerEnvironment ce = ControllerEnvironment ce =
ControllerEnvironment.getDefaultEnvironment(); ControllerEnvironment.getDefaultEnvironment();
@ -151,25 +148,42 @@ public class JInputJoyInput implements JoyInput {
} }
} }
@Override
public void setJoyRumble(int joyId, float amount) {
if (joyId >= joysticks.length) {
throw new IllegalArgumentException();
}
Controller c = joysticks[joyId].controller;
for (Rumbler r : c.getRumblers()) {
r.rumble(amount);
}
}
@Override
public void destroy() { public void destroy() {
inited = false; inited = false;
} }
@Override
public boolean isInitialized() { public boolean isInitialized() {
return inited; return inited;
} }
@Override
public void setInputListener(RawInputListener listener) { public void setInputListener(RawInputListener listener) {
this.listener = listener; this.listener = listener;
} }
@Override
public long getInputTimeNanos() { public long getInputTimeNanos() {
return 0; return 0;
} }
protected class JInputJoystick extends AbstractJoystick { private static class JInputJoystick extends AbstractJoystick {
private JoystickAxis nullAxis; private final JoystickAxis nullAxis;
private Controller controller; private Controller controller;
private JoystickAxis xAxis; private JoystickAxis xAxis;
private JoystickAxis yAxis; private JoystickAxis yAxis;
@ -216,8 +230,8 @@ public class JInputJoyInput implements JoyInput {
String name = comp.getName(); String name = comp.getName();
String original = id.getName(); String original = id.getName();
String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
if( name != original ) { if (!logicalId.equals(original)) {
logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); logger.log(Level.FINE, "Remapped:{0} to:{1}", new Object[]{original, logicalId});
} }
JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(),
@ -238,8 +252,8 @@ public class JInputJoyInput implements JoyInput {
String name = comp.getName(); String name = comp.getName();
String original = id.getName(); String original = id.getName();
String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
if( name != original ) { if (!logicalId.equals(original)) {
logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); logger.log(Level.FINE, "Remapped:{0} to:{1}", new Object[]{original, logicalId});
} }
JoystickAxis axis = new DefaultJoystickAxis( getInputManager(), JoystickAxis axis = new DefaultJoystickAxis( getInputManager(),

@ -1,529 +0,0 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.renderer.jogl;
import com.jme3.renderer.RendererException;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.image.ColorSpace;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GL2ES3;
import com.jogamp.opengl.GL2GL3;
import com.jogamp.opengl.GLContext;
public class TextureUtil {
private static boolean abgrToRgbaConversionEnabled = false;
public static int convertTextureFormat(Format fmt) {
switch (fmt) {
case Alpha8:
return GL.GL_ALPHA;
case Luminance8Alpha8:
return GL.GL_LUMINANCE_ALPHA;
case Luminance8:
return GL.GL_LUMINANCE;
case BGR8:
case RGB8:
case RGB565:
return GL.GL_RGB;
case RGB5A1:
case RGBA8:
return GL.GL_RGBA;
case Depth:
return GL2ES2.GL_DEPTH_COMPONENT;
default:
throw new UnsupportedOperationException("Unrecognized format: " + fmt);
}
}
public static class GLImageFormat {
int internalFormat;
int format;
int dataType;
boolean compressed;
public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) {
this.internalFormat = internalFormat;
this.format = format;
this.dataType = dataType;
this.compressed = compressed;
}
}
private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length];
private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){
formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed);
}
static {
// Alpha formats
setFormat(Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, false);
// Luminance formats
setFormat(Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.Luminance16F, GL2.GL_LUMINANCE16F, GL.GL_LUMINANCE, GL.GL_HALF_FLOAT, false);
setFormat(Format.Luminance32F, GL2.GL_LUMINANCE32F, GL.GL_LUMINANCE, GL.GL_FLOAT, false);
// Luminance alpha formats
setFormat(Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.Luminance16FAlpha16F, GL2.GL_LUMINANCE_ALPHA16F, GL.GL_LUMINANCE_ALPHA, GL.GL_HALF_FLOAT, false);
// Depth formats
setFormat(Format.Depth, GL2ES2.GL_DEPTH_COMPONENT, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT, false);
setFormat(Format.Depth24, GL.GL_DEPTH_COMPONENT24, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false);
setFormat(Format.Depth32, GL.GL_DEPTH_COMPONENT32, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false);
setFormat(Format.Depth32F, GL2GL3.GL_DEPTH_COMPONENT32F, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, false);
// Depth stencil formats
setFormat(Format.Depth24Stencil8, GL.GL_DEPTH24_STENCIL8, GL.GL_DEPTH_STENCIL, GL.GL_UNSIGNED_INT_24_8, false);
// RGB formats
setFormat(Format.BGR8, GL.GL_RGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.ARGB8, GL.GL_RGBA8, GL.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8_REV, false);
setFormat(Format.BGRA8, GL.GL_RGBA8, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.RGB8, GL.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.RGB16F, GL2ES2.GL_RGB16F, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
setFormat(Format.RGB32F, GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false);
// Special RGB formats
setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false);
setFormat(Format.RGB9E5, GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false);
setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
// RGBA formats
setFormat(Format.ABGR8, GL.GL_RGBA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1, false);
setFormat(Format.RGBA8, GL.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
setFormat(Format.RGBA16F, GL2ES2.GL_RGBA16F, GL.GL_RGBA, GL.GL_HALF_FLOAT, false);
setFormat(Format.RGBA32F, GL.GL_RGBA32F, GL.GL_RGBA, GL.GL_FLOAT, false);
// DXT formats
setFormat(Format.DXT1, GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true);
setFormat(Format.DXT1A, GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
setFormat(Format.DXT3, GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
setFormat(Format.DXT5, GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
}
//sRGB formats
private static final GLImageFormat sRGB_RGB8 = new GLImageFormat(GL2.GL_SRGB8,GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false);
private static final GLImageFormat sRGB_RGBA8 = new GLImageFormat(GL.GL_SRGB8_ALPHA8,GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
private static final GLImageFormat sRGB_Luminance8 = new GLImageFormat(GL2.GL_SLUMINANCE8,GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false);
private static final GLImageFormat sRGB_LuminanceAlpha8 = new GLImageFormat(GL2.GL_SLUMINANCE8_ALPHA8,GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false);
private static final GLImageFormat sRGB_BGR8 = new GLImageFormat(GL2.GL_SRGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false);
private static final GLImageFormat sRGB_ABGR8 = new GLImageFormat(GL2.GL_SRGB8_ALPHA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false);
//FIXME cannot find GL_COMPRESSED_RGB_S3TC_DXT1,GL_COMPRESSED_RGBA_S3TC_DXT1,GL_COMPRESSED_RGB_S3TC_DXT3,GL_COMPRESSED_RGB_S3TC_DXT5 in JOGL used constants
//GL_COMPRESSED_RGB_S3TC_DXT1 = 33776;
//GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 33777;
//GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 33778;
//GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779;
private static final GLImageFormat sRGB_DXT1 = new GLImageFormat(33776, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true);
private static final GLImageFormat sRGB_DXT1A = new GLImageFormat( 33777, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
private static final GLImageFormat sRGB_DXT3 = new GLImageFormat(33778, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
private static final GLImageFormat sRGB_DXT5 = new GLImageFormat( 33779, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
public static GLImageFormat getImageFormat(Format fmt, boolean isSrgb){
GL gl = GLContext.getCurrentGL();
switch (fmt){
case ABGR8:
if (!gl.isExtensionAvailable("GL_EXT_abgr") && !abgrToRgbaConversionEnabled) {
setFormat(Format.ABGR8, GL.GL_RGBA, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
abgrToRgbaConversionEnabled = true;
}
break;
case BGR8:
if (!gl.isExtensionAvailable("GL_VERSION_1_2") && !gl.isExtensionAvailable("EXT_bgra")){
return null;
}
break;
case DXT1:
case DXT1A:
case DXT3:
case DXT5:
if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc")) {
return null;
}
break;
case Depth:
case Depth16:
case Depth24:
case Depth32:
if (!gl.isExtensionAvailable("GL_VERSION_1_4") && !gl.isExtensionAvailable("ARB_depth_texture")){
return null;
}
break;
case Depth24Stencil8:
if (!gl.isExtensionAvailable("GL_VERSION_3_0")){
return null;
}
break;
case Luminance16F:
case Luminance16FAlpha16F:
case Luminance32F:
if (!gl.isExtensionAvailable("GL_ARB_texture_float")){
return null;
}
break;
case RGB16F:
case RGB32F:
case RGBA16F:
case RGBA32F:
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_ARB_texture_float")){
return null;
}
break;
case Depth32F:
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_NV_depth_buffer_float")){
return null;
}
break;
case RGB9E5:
case RGB16F_to_RGB9E5:
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")){
return null;
}
break;
case RGB111110F:
case RGB16F_to_RGB111110F:
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_packed_float")){
return null;
}
break;
}
if(isSrgb){
return getSrgbFormat(fmt);
}
return formatToGL[fmt.ordinal()];
}
public static GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) {
GLImageFormat glFmt = getImageFormat(fmt, isSrgb);
if (glFmt == null) {
throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware.");
}
return glFmt;
}
private static GLImageFormat getSrgbFormat(Format fmt){
switch (fmt){
case RGB8 : return sRGB_RGB8;
case RGBA8 : return sRGB_RGBA8;
case BGR8 : return sRGB_BGR8;
case ABGR8 : return sRGB_ABGR8;
case Luminance8 : return sRGB_Luminance8;
case Luminance8Alpha8 : return sRGB_LuminanceAlpha8;
case DXT1 : return sRGB_DXT1;
case DXT1A : return sRGB_DXT1A;
case DXT3 : return sRGB_DXT3;
case DXT5 : return sRGB_DXT5;
default : Logger.getLogger(TextureUtil.class.getName()).log(Level.WARNING, "Format {0} has no sRGB equivalent, using linear format.", fmt.toString());
return formatToGL[fmt.ordinal()];
}
}
public static void uploadTexture(Image image,
int target,
int index,
int border,
boolean linearizeSrgb){
GL gl = GLContext.getCurrentGL();
Image.Format fmt = image.getFormat();
GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb);
ByteBuffer data;
if (index >= 0 && image.getData() != null && image.getData().size() > 0){
data = image.getData(index);
}else{
data = null;
}
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (data != null) {
if (abgrToRgbaConversionEnabled) {
convertABGRtoRGBA(data);
}
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
}
int[] mipSizes = image.getMipMapSizes();
int pos = 0;
// TODO: Remove unneccessary allocation
if (mipSizes == null){
if (data != null)
mipSizes = new int[]{ data.capacity() };
else
mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
}
boolean subtex = false;
int samples = image.getMultiSamples();
for (int i = 0; i < mipSizes.length; i++){
int mipWidth = Math.max(1, width >> i);
int mipHeight = Math.max(1, height >> i);
int mipDepth = Math.max(1, depth >> i);
if (data != null){
data.position(pos);
data.limit(pos + mipSizes[i]);
}
if (glFmt.compressed && data != null){
if (target == GL2ES2.GL_TEXTURE_3D){
gl.getGL2ES2().glCompressedTexImage3D(target,
i,
glFmt.internalFormat,
mipWidth,
mipHeight,
mipDepth,
data.remaining(),
border,
data);
}else{
//all other targets use 2D: array, cubemap, 2d
gl.glCompressedTexImage2D(target,
i,
glFmt.internalFormat,
mipWidth,
mipHeight,
data.remaining(),
border,
data);
}
}else{
if (target == GL2ES2.GL_TEXTURE_3D){
gl.getGL2ES2().glTexImage3D(target,
i,
glFmt.internalFormat,
mipWidth,
mipHeight,
mipDepth,
border,
glFmt.format,
glFmt.dataType,
data);
}else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){
// prepare data for 2D array
// or upload slice
if (index == -1){
gl.getGL2ES2().glTexImage3D(target,
0,
glFmt.internalFormat,
mipWidth,
mipHeight,
image.getData().size(), //# of slices
border,
glFmt.format,
glFmt.dataType,
data);
}else{
gl.getGL2ES2().glTexSubImage3D(target,
i, // level
0, // xoffset
0, // yoffset
index, // zoffset
width, // width
height, // height
1, // depth
glFmt.format,
glFmt.dataType,
data);
}
}else{
if (subtex){
if (samples > 1){
throw new IllegalStateException("Cannot update multisample textures");
}
gl.glTexSubImage2D(target,
i,
0, 0,
mipWidth, mipHeight,
glFmt.format,
glFmt.dataType,
data);
}else{
if (samples > 1){
if (gl.isGL2GL3()) {
gl.getGL3().glTexImage2DMultisample(target,
samples,
glFmt.internalFormat,
mipWidth,
mipHeight,
true);
}
} else {
gl.glTexImage2D(target,
i,
glFmt.internalFormat,
mipWidth,
mipHeight,
border,
glFmt.format,
glFmt.dataType,
data);
}
}
}
}
pos += mipSizes[i];
}
}
private static void convertABGRtoRGBA(ByteBuffer buffer) {
for (int i = 0; i < buffer.capacity(); i++) {
int a = buffer.get(i++);
int b = buffer.get(i++);
int g = buffer.get(i++);
int r = buffer.get(i);
buffer.put(i - 3, (byte) r);
buffer.put(i - 2, (byte) g);
buffer.put(i - 1, (byte) b);
buffer.put(i, (byte) a);
}
}
/**
* Update the texture currently bound to target at with data from the given Image at position x and y. The parameter
* index is used as the zoffset in case a 3d texture or texture 2d array is being updated.
*
* @param image Image with the source data (this data will be put into the texture)
* @param target the target texture
* @param index the mipmap level to update
* @param x the x position where to put the image in the texture
* @param y the y position where to put the image in the texture
*/
public static void uploadSubTexture(
Image image,
int target,
int index,
int x,
int y,
boolean linearizeSrgb) {
GL gl = GLContext.getCurrentGL();
Image.Format fmt = image.getFormat();
GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb);
ByteBuffer data = null;
if (index >= 0 && image.getData() != null && image.getData().size() > 0) {
data = image.getData(index);
}
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (data != null) {
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
}
int[] mipSizes = image.getMipMapSizes();
int pos = 0;
// TODO: Remove unneccessary allocation
if (mipSizes == null){
if (data != null) {
mipSizes = new int[]{ data.capacity() };
} else {
mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
}
}
int samples = image.getMultiSamples();
for (int i = 0; i < mipSizes.length; i++){
int mipWidth = Math.max(1, width >> i);
int mipHeight = Math.max(1, height >> i);
int mipDepth = Math.max(1, depth >> i);
if (data != null){
data.position(pos);
data.limit(pos + mipSizes[i]);
}
// to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each
// gl*Image call in an attempt to unclutter things a bit
pos += mipSizes[i];
int glFmtInternal = glFmt.internalFormat;
int glFmtFormat = glFmt.format;
int glFmtDataType = glFmt.dataType;
if (glFmt.compressed && data != null){
if (target == GL2ES2.GL_TEXTURE_3D){
gl.getGL2ES2().glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data.limit(), data);
continue;
}
// all other targets use 2D: array, cubemap, 2d
gl.getGL2ES2().glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data.limit(), data);
continue;
}
if (target == GL2ES2.GL_TEXTURE_3D){
gl.getGL2ES2().glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data);
continue;
}
if (samples > 1){
throw new IllegalStateException("Cannot update multisample textures");
}
gl.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data);
continue;
}
}
}

@ -5,5 +5,8 @@ if (!hasProperty('mainClass')) {
dependencies { dependencies {
compile project(':jme3-core') compile project(':jme3-core')
compile project(':jme3-desktop') compile project(':jme3-desktop')
compile project(':jme3-jinput')
compile 'org.lwjgl.lwjgl:lwjgl:2.9.3' compile 'org.lwjgl.lwjgl:lwjgl:2.9.3'
testCompile project(path: ':jme3-core', configuration: 'testOutput')
} }

@ -36,9 +36,9 @@ import com.jme3.input.JoyInput;
import com.jme3.input.KeyInput; import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput; import com.jme3.input.MouseInput;
import com.jme3.input.TouchInput; import com.jme3.input.TouchInput;
import com.jme3.input.lwjgl.JInputJoyInput;
import com.jme3.input.lwjgl.LwjglKeyInput; import com.jme3.input.lwjgl.LwjglKeyInput;
import com.jme3.input.lwjgl.LwjglMouseInput; import com.jme3.input.lwjgl.LwjglMouseInput;
import com.jme3.input.jinput.JInputJoyInput;
import com.jme3.system.AppSettings; import com.jme3.system.AppSettings;
import com.jme3.system.JmeSystem; import com.jme3.system.JmeSystem;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;

@ -32,7 +32,7 @@
package com.jme3.system.lwjgl; package com.jme3.system.lwjgl;
import com.jme3.input.lwjgl.JInputJoyInput; import com.jme3.input.jinput.JInputJoyInput;
import com.jme3.input.lwjgl.LwjglKeyInput; import com.jme3.input.lwjgl.LwjglKeyInput;
import com.jme3.input.lwjgl.LwjglMouseInput; import com.jme3.input.lwjgl.LwjglMouseInput;
import com.jme3.renderer.Renderer; import com.jme3.renderer.Renderer;
@ -53,7 +53,6 @@ import com.jme3.renderer.opengl.GLTiming;
import com.jme3.renderer.opengl.GLTimingState; import com.jme3.renderer.opengl.GLTimingState;
import com.jme3.renderer.opengl.GLTracer; import com.jme3.renderer.opengl.GLTracer;
import com.jme3.system.*; import com.jme3.system.*;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
@ -163,17 +162,18 @@ public abstract class LwjglContext implements JmeContext {
if (JmeSystem.isLowPermissions()) { if (JmeSystem.isLowPermissions()) {
return; return;
} }
String extractPath = NativeLibraryLoader.getExtractionFolder().getAbsolutePath();
if ("LWJGL".equals(settings.getAudioRenderer())) { if ("LWJGL".equals(settings.getAudioRenderer())) {
NativeLibraryLoader.loadNativeLibrary("openal", true); NativeLibraryLoader.loadNativeLibrary("openal", true);
} }
if (settings.useJoysticks()) {
NativeLibraryLoader.loadNativeLibrary("jinput", true);
NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true);
}
if (NativeLibraryLoader.isUsingNativeBullet()) { if (NativeLibraryLoader.isUsingNativeBullet()) {
NativeLibraryLoader.loadNativeLibrary("bulletjme", true); NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
} }
NativeLibraryLoader.loadNativeLibrary("lwjgl", true);
System.setProperty("org.lwjgl.librarypath", extractPath);
NativeLibraryLoader.loadNativeLibrary("lwjgl", true, false);
} }
protected int getNumSamplesToUse() { protected int getNumSamplesToUse() {
int samples = 0; int samples = 0;

@ -148,12 +148,16 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable {
super.internalDestroy(); super.internalDestroy();
} }
@Override
public void run(){ public void run(){
loadNatives(); loadNatives();
logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion()); logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion());
initInThread(); initInThread();
while (!needClose.get()){ while (true) {
runLoop(); runLoop();
if (needClose.get()) {
break;
}
} }
deinitInThread(); deinitInThread();
} }

@ -7,6 +7,7 @@ def lwjglVersion = '3.0.0b'
dependencies { dependencies {
compile project(':jme3-core') compile project(':jme3-core')
compile project(':jme3-desktop') compile project(':jme3-desktop')
compile project(':jme3-jinput')
compile "org.lwjgl:lwjgl:${lwjglVersion}" compile "org.lwjgl:lwjgl:${lwjglVersion}"
compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-windows" compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-windows"

@ -32,6 +32,7 @@
package com.jme3.system.lwjgl; package com.jme3.system.lwjgl;
import com.jme3.input.jinput.JInputJoyInput;
import com.jme3.input.lwjgl.GlfwJoystickInput; import com.jme3.input.lwjgl.GlfwJoystickInput;
import com.jme3.input.lwjgl.GlfwKeyInput; import com.jme3.input.lwjgl.GlfwKeyInput;
import com.jme3.input.lwjgl.GlfwMouseInput; import com.jme3.input.lwjgl.GlfwMouseInput;
@ -74,7 +75,7 @@ public abstract class LwjglContext implements JmeContext {
protected Renderer renderer; protected Renderer renderer;
protected GlfwKeyInput keyInput; protected GlfwKeyInput keyInput;
protected GlfwMouseInput mouseInput; protected GlfwMouseInput mouseInput;
protected GlfwJoystickInput joyInput; protected JInputJoyInput joyInput;
protected Timer timer; protected Timer timer;
protected SystemListener listener; protected SystemListener listener;

@ -36,6 +36,7 @@ import com.jme3.input.JoyInput;
import com.jme3.input.KeyInput; import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput; import com.jme3.input.MouseInput;
import com.jme3.input.TouchInput; import com.jme3.input.TouchInput;
import com.jme3.input.jinput.JInputJoyInput;
import com.jme3.input.lwjgl.GlfwJoystickInput; import com.jme3.input.lwjgl.GlfwJoystickInput;
import com.jme3.input.lwjgl.GlfwKeyInput; import com.jme3.input.lwjgl.GlfwKeyInput;
import com.jme3.input.lwjgl.GlfwMouseInput; import com.jme3.input.lwjgl.GlfwMouseInput;
@ -465,7 +466,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
public JoyInput getJoyInput() { public JoyInput getJoyInput() {
if (joyInput == null) { if (joyInput == null) {
joyInput = new GlfwJoystickInput(); joyInput = new JInputJoyInput();
} }
return joyInput; return joyInput;
} }

@ -5,6 +5,7 @@ if (!hasProperty('mainClass')) {
sourceSets { sourceSets {
main { main {
java { java {
srcDir 'src/main/java'
srcDir 'src/ogre/java' srcDir 'src/ogre/java'
srcDir 'src/fbx/java' srcDir 'src/fbx/java'
srcDir 'src/xml/java' srcDir 'src/xml/java'

@ -150,7 +150,7 @@ public class FbxLoader implements AssetLoader {
private void loadData(InputStream stream) throws IOException { private void loadData(InputStream stream) throws IOException {
FbxFile scene = FbxReader.readFBX(stream); FbxFile scene = FbxReader.readFBX(stream);
FbxDump.dumpFile(scene); // FbxDump.dumpFile(scene);
// TODO: Load FBX object templates // TODO: Load FBX object templates
@ -346,33 +346,33 @@ public class FbxLoader implements AssetLoader {
duration = pair.getDuration(); duration = pair.getDuration();
if (pair.node instanceof FbxLimbNode) { if (pair.node instanceof FbxLimbNode) {
// Find the spatial that has the skeleton for this limb. // // Find the spatial that has the skeleton for this limb.
FbxLimbNode limbNode = (FbxLimbNode) pair.node; // FbxLimbNode limbNode = (FbxLimbNode) pair.node;
Bone bone = limbNode.getJmeBone(); // Bone bone = limbNode.getJmeBone();
Spatial jmeSpatial = limbNode.getSkeletonHolder().getJmeObject(); // Spatial jmeSpatial = limbNode.getSkeletonRoot().getJmeObject();
Skeleton skeleton = limbNode.getSkeletonHolder().getJmeSkeleton(); // Skeleton skeleton = limbNode.getSkeletonRoot().getJmeSkeleton();
//
// Get the animation control (create if missing). // // Get the animation control (create if missing).
AnimControl animControl = jmeSpatial.getControl(AnimControl.class); // AnimControl animControl = jmeSpatial.getControl(AnimControl.class);
if (animControl.getSkeleton() != skeleton) { // if (animControl.getSkeleton() != skeleton) {
throw new UnsupportedOperationException(); // throw new UnsupportedOperationException();
} // }
//
// Get the animation (create if missing). // // Get the animation (create if missing).
Animation anim = animControl.getAnim(animName); // Animation anim = animControl.getAnim(animName);
if (anim == null) { // if (anim == null) {
anim = new Animation(animName, duration); // anim = new Animation(animName, duration);
animControl.addAnim(anim); // animControl.addAnim(anim);
} // }
//
// Find the bone index from the spatial's skeleton. // // Find the bone index from the spatial's skeleton.
int boneIndex = skeleton.getBoneIndex(bone); // int boneIndex = skeleton.getBoneIndex(bone);
//
// Generate the bone track. // // Generate the bone track.
BoneTrack bt = pair.toJmeBoneTrack(boneIndex, bone.getBindInverseTransform()); // BoneTrack bt = pair.toJmeBoneTrack(boneIndex, bone.getBindInverseTransform());
//
// Add the bone track to the animation. // // Add the bone track to the animation.
anim.addTrack(bt); // anim.addTrack(bt);
} else { } else {
// Create the spatial animation // Create the spatial animation
Animation anim = new Animation(animName, duration); Animation anim = new Animation(animName, duration);

@ -31,6 +31,8 @@
*/ */
package com.jme3.scene.plugins.fbx.anim; package com.jme3.scene.plugins.fbx.anim;
import com.jme3.math.Matrix4f;
public class FbxAnimUtil { public class FbxAnimUtil {
/** /**
* Conversion factor from FBX animation time unit to seconds. * Conversion factor from FBX animation time unit to seconds.
@ -41,4 +43,12 @@ public class FbxAnimUtil {
public static final String CURVE_NODE_PROPERTY_Y = "d|Y"; public static final String CURVE_NODE_PROPERTY_Y = "d|Y";
public static final String CURVE_NODE_PROPERTY_Z = "d|Z"; public static final String CURVE_NODE_PROPERTY_Z = "d|Z";
public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility"; public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility";
public static Matrix4f toMatrix4(double[] matrixData) {
float[] matrixDataFloat = new float[16];
for (int i = 0; i < matrixData.length; i++) {
matrixDataFloat[i] = (float) matrixData[i];
}
return new Matrix4f(matrixDataFloat);
}
} }

@ -56,31 +56,25 @@ public class FbxBindPose extends FbxObject<Map<FbxId, Matrix4f>> {
} }
FbxId node = null; FbxId node = null;
float[] matData = null; double[] matData = null;
for (FbxElement e : child.children) { for (FbxElement e : child.children) {
if (e.id.equals("Node")) { if (e.id.equals("Node")) {
node = FbxId.create(e.properties.get(0)); node = FbxId.create(e.properties.get(0));
} else if (e.id.equals("Matrix")) { } else if (e.id.equals("Matrix")) {
double[] matDataDoubles = (double[]) e.properties.get(0); matData = (double[]) e.properties.get(0);
if (matDataDoubles.length != 16) { if (matData.length != 16) {
// corrupt // corrupt
throw new UnsupportedOperationException("Bind pose matrix " throw new UnsupportedOperationException("Bind pose matrix "
+ "must have 16 doubles, but it has " + "must have 16 doubles, but it has "
+ matDataDoubles.length + ". Data is corrupt"); + matData.length + ". Data is corrupt");
}
matData = new float[16];
for (int i = 0; i < matDataDoubles.length; i++) {
matData[i] = (float) matDataDoubles[i];
} }
} }
} }
if (node != null && matData != null) { if (node != null && matData != null) {
Matrix4f matrix = new Matrix4f(matData); bindPose.put(node, FbxAnimUtil.toMatrix4(matData));
bindPose.put(node, matrix);
} }
} }
} }

@ -32,6 +32,7 @@
package com.jme3.scene.plugins.fbx.anim; package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.math.Matrix4f;
import com.jme3.scene.plugins.fbx.file.FbxElement; import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject; import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.logging.Level; import java.util.logging.Level;
@ -45,6 +46,10 @@ public class FbxCluster extends FbxObject {
private double[] weights; private double[] weights;
private FbxLimbNode limb; private FbxLimbNode limb;
private Matrix4f transformMatrix;
private Matrix4f transformLinkMatrix;
private Matrix4f transformAssociateModelMatrix;
public FbxCluster(AssetManager assetManager, String sceneFolderName) { public FbxCluster(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName); super(assetManager, sceneFolderName);
} }
@ -57,6 +62,15 @@ public class FbxCluster extends FbxObject {
indexes = (int[]) e.properties.get(0); indexes = (int[]) e.properties.get(0);
} else if (e.id.equals("Weights")) { } else if (e.id.equals("Weights")) {
weights = (double[]) e.properties.get(0); weights = (double[]) e.properties.get(0);
} else if (e.id.equals("Transform")) {
double[] data = (double[]) e.properties.get(0);
transformMatrix = FbxAnimUtil.toMatrix4(data);
} else if (e.id.equals("TransformLink")) {
double[] data = (double[]) e.properties.get(0);
transformLinkMatrix = FbxAnimUtil.toMatrix4(data);
} else if (e.id.equals("TransformAssociateModel")) {
double[] data = (double[]) e.properties.get(0);
transformAssociateModelMatrix = FbxAnimUtil.toMatrix4(data);
} }
} }
} }
@ -86,6 +100,18 @@ public class FbxCluster extends FbxObject {
return; return;
} }
limb = (FbxLimbNode) object; limb = (FbxLimbNode) object;
System.out.println(" ----- for limb: " + limb.getName());
System.out.println(" transform : " + transformMatrix);
System.out.println(" transform link : " + transformLinkMatrix);
System.out.println(" transform associate model : " + transformAssociateModelMatrix);
// Invert(Invert(TransformLinkMatrix) * TransformMatrix * Geometry)
Matrix4f accumMatrix = transformLinkMatrix.invert();
accumMatrix.multLocal(transformMatrix);
accumMatrix.invertLocal();
System.out.println(" limb bind pose : " + accumMatrix);
} else { } else {
unsupportedConnectObject(object); unsupportedConnectObject(object);
} }

@ -31,64 +31,22 @@
*/ */
package com.jme3.scene.plugins.fbx.anim; package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.node.FbxNode; import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.ArrayList;
import java.util.List;
public class FbxLimbNode extends FbxNode { public class FbxLimbNode extends FbxNode {
protected FbxNode skeletonHolder; protected FbxNode skeletonRoot;
protected Bone bone;
public FbxLimbNode(AssetManager assetManager, String sceneFolderName) { public FbxLimbNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName); super(assetManager, sceneFolderName);
} }
private static void createBones(FbxNode skeletonHolderNode, FbxLimbNode limb, List<Bone> bones) { public FbxNode getSkeletonRoot() {
limb.skeletonHolder = skeletonHolderNode; return skeletonRoot;
Bone parentBone = limb.getJmeBone();
bones.add(parentBone);
for (FbxNode child : limb.children) {
if (child instanceof FbxLimbNode) {
FbxLimbNode childLimb = (FbxLimbNode) child;
createBones(skeletonHolderNode, childLimb, bones);
parentBone.addChild(childLimb.getJmeBone());
}
}
} }
public static Skeleton createSkeleton(FbxNode skeletonHolderNode) { public void setSkeletonRoot(FbxNode skeletonRoot) {
if (skeletonHolderNode instanceof FbxLimbNode) { this.skeletonRoot = skeletonRoot;
throw new UnsupportedOperationException("Limb nodes cannot be skeleton holders");
}
List<Bone> bones = new ArrayList<Bone>();
for (FbxNode child : skeletonHolderNode.getChildren()) {
if (child instanceof FbxLimbNode) {
createBones(skeletonHolderNode, (FbxLimbNode) child, bones);
}
}
return new Skeleton(bones.toArray(new Bone[0]));
}
public FbxNode getSkeletonHolder() {
return skeletonHolder;
}
public Bone getJmeBone() {
if (bone == null) {
bone = new Bone(name);
bone.setBindTransforms(jmeLocalBindPose.getTranslation(),
jmeLocalBindPose.getRotation(),
jmeLocalBindPose.getScale());
}
return bone;
} }
} }

@ -0,0 +1,112 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Similar to {@link Skeleton jME skeleton} except
* contains {@link FbxLimbNode limb nodes}.
*
* This is used to determine the bone indices (for assigning clusters to meshes)
* as well as the limb hierarchy when creating the jME3 Skeleton.
*
* @author Kirill Vainer
*/
public class FbxSkeleton {
FbxLimbNode[] rootLimbs;
FbxLimbNode[] allLimbs;
HashMap<FbxLimbNode, Integer> limbToIndexMap = new HashMap<FbxLimbNode, Integer>();
private FbxSkeleton() {
}
public static void populateSkeletonData(FbxNode skeletonRoot) {
// if (skeletonRoot instanceof FbxLimbNode) {
// throw new UnsupportedOperationException("Limb node cannot be a skeleton root");
// }
//
// FbxSkeleton skeleton = new FbxSkeleton();
// skeleton.scanLimbs(skeletonRoot);
// skeletonRoot.setFbxSkeleton(skeleton);
}
private void scanLimbs(FbxNode skeletonRoot, FbxLimbNode limb, List<FbxLimbNode> limbList) {
// limb.skeletonRoot = skeletonRoot;
// limbList.add(limb);
// for (FbxNode child : limb.getChildren()) {
// if (child instanceof FbxLimbNode) {
// FbxLimbNode childLimb = (FbxLimbNode) child;
// scanLimbs(skeletonRoot, childLimb, limbList);
// }
// }
}
private void scanLimbs(FbxNode skeletonRoot) {
List<FbxLimbNode> limbList = new ArrayList<FbxLimbNode>();
List<FbxLimbNode> rootList = new ArrayList<FbxLimbNode>();
for (FbxNode child : skeletonRoot.getChildren()) {
if (child instanceof FbxLimbNode) {
FbxLimbNode limb = (FbxLimbNode) child;
rootList.add(limb);
scanLimbs(skeletonRoot, limb, limbList);
}
}
allLimbs = limbList.toArray(new FbxLimbNode[0]);
rootLimbs = rootList.toArray(new FbxLimbNode[0]);
for (int i = 0; i < allLimbs.length; i++) {
limbToIndexMap.put(allLimbs[i], i);
}
}
public int getLimbIndex(FbxLimbNode limbNode) {
return limbToIndexMap.get(limbNode);
}
public FbxLimbNode getLimb(int index) {
return allLimbs[index];
}
public FbxLimbNode[] getRootLimbs() {
return rootLimbs;
}
}

@ -31,24 +31,94 @@
*/ */
package com.jme3.scene.plugins.fbx.anim; package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.obj.FbxObject; import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> { public class FbxSkinDeformer extends FbxObject<Skeleton> {
private FbxNode skeletonRoot;
private final List<FbxCluster> clusters = new ArrayList<FbxCluster>(); private final List<FbxCluster> clusters = new ArrayList<FbxCluster>();
public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) { public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName); super(assetManager, sceneFolderName);
} }
private boolean isHierarchyCompatible(Bone thisBone, Bone otherBone) {
Transform thisTransform = thisBone.getBindInverseTransform();
Transform otherTransform = otherBone.getBindInverseTransform();
throw new UnsupportedOperationException();
}
/**
* Determine if both skin deformers can share the same
* Skeleton object and hence the same SkeletonControl / AnimControl.
*
* @param skinDeformer The skin deformer to test compatibility against.
* @return True if the skeletons are identical and can be shared, false
* otherwise.
*/
public boolean isCompatible(FbxSkinDeformer skinDeformer) {
Skeleton thisSkeleton = this.getJmeObject();
Skeleton otherSkeleton = skinDeformer.getJmeObject();
Bone[] thisRoots = thisSkeleton.getRoots();
Bone[] otherRoots = otherSkeleton.getRoots();
for (int i = 0; i < thisRoots.length; i++) {
}
throw new UnsupportedOperationException();
}
/**
* Get the root FbxNode containing the skeleton.
*
* The node should have one or more FbxLimbNodes which are
* the root limbs of the skeleton structure.
*
* This is null until prepareSkeletonData() is called.
*
* @return The root node containing the skeleton.
*/
public FbxNode getSkeletonRoot() {
return skeletonRoot;
}
/**
* Derives the skeleton from the skin deformer.
*
* The Skeleton hierarchy is derived via the {@link #getSkeletonRoot() skeleton root}
* whereas the bind poses for the bones is derived from the
* {@link #getClusters() clusters}.
*
* FbxLimbNode.prepareSkeletonData() must have been called first
* The bone's bind pose depends on each cluster's TransformLinkMatrix
* and TransformMatrix.
* The bone's bind pose is derived as follows:
* <code><pre>
* Invert(Invert(TransformLinkMatrix) * TransformMatrix * Geometry)
* </code></pre>
*
* @return The skeleton as described by this skin deformer.
*/
@Override @Override
protected List<FbxCluster> toJmeObject() { protected Skeleton toJmeObject() {
throw new UnsupportedOperationException();
}
/**
* Get the clusters attached to this skin deformer.
*
* @return The skin deformer's clusters.
*/
public List<FbxCluster> getClusters() {
return clusters; return clusters;
} }
@Override @Override
public void connectObject(FbxObject object) { public void connectObject(FbxObject object) {
if (object instanceof FbxCluster) { if (object instanceof FbxCluster) {
@ -62,5 +132,5 @@ public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> {
public void connectObjectProperty(FbxObject object, String property) { public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property); unsupportedConnectObjectProperty(object, property);
} }
} }

@ -34,6 +34,7 @@ package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.BoneTrack; import com.jme3.animation.BoneTrack;
import com.jme3.animation.SpatialTrack; import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track; import com.jme3.animation.Track;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Transform; import com.jme3.math.Transform;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
@ -98,6 +99,7 @@ public final class FbxToJmeTrack {
} }
private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) { private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) {
/*
Transform t = new Transform(); Transform t = new Transform();
t.setTranslation(translation); t.setTranslation(translation);
t.setRotation(rotation); t.setRotation(rotation);
@ -111,6 +113,24 @@ public final class FbxToJmeTrack {
if (scale != null) { if (scale != null) {
t.getScale(scale); t.getScale(scale);
} }
*/
Matrix4f mat = new Matrix4f();
mat.setTranslation(translation);
mat.setRotationQuaternion(rotation);
if (scale != null) {
mat.setScale(scale);
}
Matrix4f mat2 = inverseBindPose.toTransformMatrix();
mat2.multLocal(mat);
mat = mat2;
mat.toTranslationVector(translation);
mat.toRotationQuat(rotation);
if (scale != null) {
mat.toScaleVector(scale);
}
} }
private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) { private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) {
@ -154,7 +174,7 @@ public final class FbxToJmeTrack {
if (i > 0) { if (i > 0) {
if (rotations[i - 1].dot(rotations[i]) < 0) { if (rotations[i - 1].dot(rotations[i]) < 0) {
System.out.println("rotation will go the long way, oh noes"); System.out.println("rotation will go the long way, oh noes");
rotations[i - 1].negate(); rotations[i].negate();
} }
} }
} else { } else {

@ -31,6 +31,9 @@
*/ */
package com.jme3.scene.plugins.fbx.file; package com.jme3.scene.plugins.fbx.file;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.Array; import java.lang.reflect.Array;
@ -88,6 +91,28 @@ public final class FbxDump {
dumpFile(file, System.out); dumpFile(file, System.out);
} }
/**
* Dump FBX to standard output.
*
* @param file the file to dump.
*/
public static void dumpFile(String file) {
InputStream in = null;
try {
in = new FileInputStream(file);
FbxFile scene = FbxReader.readFBX(in);
FbxDump.dumpFile(scene);
} catch (IOException ex) {
throw new RuntimeException(ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) { }
}
}
}
/** /**
* Dump FBX to the given output stream. * Dump FBX to the given output stream.
* *

@ -307,7 +307,11 @@ public class FbxMaterial extends FbxObject<Material> {
if (useAlphaBlend) { if (useAlphaBlend) {
// No idea if this is a transparent or translucent model, gotta guess.. // No idea if this is a transparent or translucent model, gotta guess..
mat.setTransparent(true); mat.setTransparent(true);
mat.setFloat("AlphaDiscardThreshold", 0.01f);
// Commenting this out for now. It causes extra shaders to be
// used and is less efficient due to usage of "discard".
// mat.setFloat("AlphaDiscardThreshold", 0.01f);
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
} }

@ -111,43 +111,44 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
} }
public void applyCluster(FbxCluster cluster) { public void applyCluster(FbxCluster cluster) {
if (boneIndices == null) { // if (boneIndices == null) {
boneIndices = new ArrayList[positions.length]; // boneIndices = new ArrayList[positions.length];
boneWeights = new ArrayList[positions.length]; // boneWeights = new ArrayList[positions.length];
} // }
//
FbxLimbNode limb = cluster.getLimb(); // FbxLimbNode limb = cluster.getLimb();
Bone bone = limb.getJmeBone(); // Bone bone = limb.getJmeBone();
Skeleton skeleton = limb.getSkeletonHolder().getJmeSkeleton(); // Skeleton skeleton = limb.getSkeletonRoot().getJmeSkeleton();
int boneIndex = skeleton.getBoneIndex(bone); // int boneIndex = skeleton.getBoneIndex(bone);
//
int[] positionIndices = cluster.getVertexIndices(); // int[] positionIndices = cluster.getVertexIndices();
double[] weights = cluster.getWeights(); // double[] weights = cluster.getWeights();
//
for (int i = 0; i < positionIndices.length; i++) { // for (int i = 0; i < positionIndices.length; i++) {
int positionIndex = positionIndices[i]; // int positionIndex = positionIndices[i];
float boneWeight = (float)weights[i]; // float boneWeight = (float)weights[i];
//
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex]; // ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex];
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex]; // ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex];
//
if (boneIndicesForVertex == null) { // if (boneIndicesForVertex == null) {
boneIndicesForVertex = new ArrayList<Integer>(); // boneIndicesForVertex = new ArrayList<Integer>();
boneWeightsForVertex = new ArrayList<Float>(); // boneWeightsForVertex = new ArrayList<Float>();
boneIndices[positionIndex] = boneIndicesForVertex; // boneIndices[positionIndex] = boneIndicesForVertex;
boneWeights[positionIndex] = boneWeightsForVertex; // boneWeights[positionIndex] = boneWeightsForVertex;
} // }
//
boneIndicesForVertex.add(boneIndex); // boneIndicesForVertex.add(boneIndex);
boneWeightsForVertex.add(boneWeight); // boneWeightsForVertex.add(boneWeight);
} // }
} }
@Override @Override
public void connectObject(FbxObject object) { public void connectObject(FbxObject object) {
if (object instanceof FbxSkinDeformer) { if (object instanceof FbxSkinDeformer) {
if (skinDeformer != null) { if (skinDeformer != null) {
logger.log(Level.WARNING, "This mesh already has a skin deformer attached. Ignoring."); logger.log(Level.WARNING, "This mesh already has a skin "
+ "deformer attached: {0}. Ignoring.", this);
return; return;
} }
skinDeformer = (FbxSkinDeformer) object; skinDeformer = (FbxSkinDeformer) object;
@ -209,7 +210,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
protected IntMap<Mesh> toJmeObject() { protected IntMap<Mesh> toJmeObject() {
// Load clusters from SkinDeformer // Load clusters from SkinDeformer
if (skinDeformer != null) { if (skinDeformer != null) {
for (FbxCluster cluster : skinDeformer.getJmeObject()) { for (FbxCluster cluster : skinDeformer.getClusters()) {
applyCluster(cluster); applyCluster(cluster);
} }
} }
@ -237,7 +238,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
if (jmeMeshes.size() == 0) { if (jmeMeshes.size() == 0) {
// When will this actually happen? Not sure. // When will this actually happen? Not sure.
logger.log(Level.WARNING, "Empty FBX mesh found (unusual)."); logger.log(Level.WARNING, "Empty FBX mesh found: {0} (unusual).", this);
} }
// IMPORTANT: If we have a -1 entry, those are triangles // IMPORTANT: If we have a -1 entry, those are triangles
@ -245,7 +246,8 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
// It makes sense only if the mesh uses a single material! // It makes sense only if the mesh uses a single material!
if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) { if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) {
logger.log(Level.WARNING, "Mesh has polygons with no material " logger.log(Level.WARNING, "Mesh has polygons with no material "
+ "indices (unusual) - they will use material index 0."); + "indices: {0} (unusual) - "
+ "they will use material index 0.", this);
} }
return jmeMeshes; return jmeMeshes;

@ -53,6 +53,7 @@ import com.jme3.scene.debug.SkeletonDebugger;
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode;
import com.jme3.scene.plugins.fbx.anim.FbxCluster; import com.jme3.scene.plugins.fbx.anim.FbxCluster;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.anim.FbxSkeleton;
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer;
import com.jme3.scene.plugins.fbx.file.FbxElement; import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.material.FbxImage; import com.jme3.scene.plugins.fbx.material.FbxImage;
@ -108,7 +109,7 @@ public class FbxNode extends FbxObject<Spatial> {
/** /**
* For FBX nodes that contain a skeleton (i.e. FBX limbs). * For FBX nodes that contain a skeleton (i.e. FBX limbs).
*/ */
protected Skeleton skeleton; protected FbxSkeleton skeleton;
protected final Transform jmeWorldNodeTransform = new Transform(); protected final Transform jmeWorldNodeTransform = new Transform();
protected final Transform jmeLocalNodeTransform = new Transform(); protected final Transform jmeLocalNodeTransform = new Transform();
@ -293,7 +294,8 @@ public class FbxNode extends FbxObject<Spatial> {
float z = ((Double) e2.properties.get(6)).floatValue(); float z = ((Double) e2.properties.get(6)).floatValue();
userDataValue = new Vector3f(x, y, z); userDataValue = new Vector3f(x, y, z);
} else { } else {
logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType); logger.log(Level.WARNING, "Unsupported user data type: {0}. "
+ "Ignoring.", userDataType);
continue; continue;
} }
@ -329,6 +331,9 @@ public class FbxNode extends FbxObject<Spatial> {
// Material index does not exist. Create default material. // Material index does not exist. Create default material.
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
jmeMat.setReceivesShadows(true); jmeMat.setReceivesShadows(true);
logger.log(Level.WARNING, "Material index {0} is undefined in: {1}. "
+ "Will use default material.",
new Object[]{materialIndex, this});
} else { } else {
FbxMaterial fbxMat = materials.get(materialIndex); FbxMaterial fbxMat = materials.get(materialIndex);
jmeMat = fbxMat.getJmeObject(); jmeMat = fbxMat.getJmeObject();
@ -376,11 +381,11 @@ public class FbxNode extends FbxObject<Spatial> {
FbxNode preferredParent = null; FbxNode preferredParent = null;
if (deformer != null) { if (deformer != null) {
for (FbxCluster cluster : deformer.getJmeObject()) { for (FbxCluster cluster : deformer.getClusters()) {
FbxLimbNode limb = cluster.getLimb(); FbxLimbNode limb = cluster.getLimb();
if (preferredParent == null) { if (preferredParent == null) {
preferredParent = limb.getSkeletonHolder(); preferredParent = limb.getSkeletonRoot();
} else if (preferredParent != limb.getSkeletonHolder()) { } else if (preferredParent != limb.getSkeletonRoot()) {
logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. " logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. "
+ "Only one skeleton will work, ignoring other skeletons."); + "Only one skeleton will work, ignoring other skeletons.");
} }
@ -400,7 +405,8 @@ public class FbxNode extends FbxObject<Spatial> {
if (jmeMeshes == null || jmeMeshes.size() == 0) { if (jmeMeshes == null || jmeMeshes.size() == 0) {
// No meshes found on FBXMesh (??) // No meshes found on FBXMesh (??)
logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node."); logger.log(Level.WARNING, "No meshes could be loaded: {0}. "
+ "Creating empty node.", this);
spatial = new Node(getName() + "-node"); spatial = new Node(getName() + "-node");
} else { } else {
// Multiple jME3 geometries required for a single FBXMesh. // Multiple jME3 geometries required for a single FBXMesh.
@ -437,7 +443,7 @@ public class FbxNode extends FbxObject<Spatial> {
if (!FastMath.approximateEquals(localScale.x, localScale.y) || if (!FastMath.approximateEquals(localScale.x, localScale.y) ||
!FastMath.approximateEquals(localScale.x, localScale.z)) { !FastMath.approximateEquals(localScale.x, localScale.z)) {
logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " + logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " +
"The model may appear distorted."); "The model {1} may appear distorted.", this);
} }
} }
@ -479,8 +485,8 @@ public class FbxNode extends FbxObject<Spatial> {
if (fbxNode.skeleton != null) { if (fbxNode.skeleton != null) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode); // fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
System.out.println("created skeleton: " + fbxNode.skeleton); // System.out.println("created skeleton: " + fbxNode.skeleton);
} }
} }
@ -518,19 +524,19 @@ public class FbxNode extends FbxObject<Spatial> {
} }
} }
if (fbxNode.skeleton != null) { // if (fbxNode.skeleton != null) {
jmeSpatial.addControl(new AnimControl(fbxNode.skeleton)); // jmeSpatial.addControl(new AnimControl(fbxNode.skeleton));
jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton)); // jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton));
//
SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton); // SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton);
Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); // Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.getAdditionalRenderState().setWireframe(true); // mat.getAdditionalRenderState().setWireframe(true);
mat.getAdditionalRenderState().setDepthTest(false); // mat.getAdditionalRenderState().setDepthTest(false);
mat.setColor("Color", ColorRGBA.Green); // mat.setColor("Color", ColorRGBA.Green);
sd.setMaterial(mat); // sd.setMaterial(mat);
//
((Node)jmeSpatial).attachChild(sd); // ((Node)jmeSpatial).attachChild(sd);
} // }
return jmeSpatial; return jmeSpatial;
} }
@ -543,10 +549,18 @@ public class FbxNode extends FbxObject<Spatial> {
// return limb; // return limb;
// } // }
public Skeleton getJmeSkeleton() { // public Skeleton getJmeSkeleton() {
// return skeleton;
// }
public FbxSkeleton getFbxSkeleton() {
return skeleton; return skeleton;
} }
public void setFbxSkeleton(FbxSkeleton skeleton) {
this.skeleton = skeleton;
}
public List<FbxNode> getChildren() { public List<FbxNode> getChildren() {
return children; return children;
} }

@ -37,6 +37,7 @@ import com.jme3.scene.VertexBuffer;
import com.jme3.scene.mesh.IndexBuffer; import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.scene.mesh.IndexIntBuffer; import com.jme3.scene.mesh.IndexIntBuffer;
import com.jme3.scene.mesh.IndexShortBuffer; import com.jme3.scene.mesh.IndexShortBuffer;
import com.jme3.scene.plugins.triangulator.EarClippingTriangulator;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap; import com.jme3.util.IntMap;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -172,23 +173,40 @@ public final class IrUtils {
} }
} }
private static void dumpPoly(IrPolygon polygon) {
System.out.println("Polygon with " + polygon.vertices.length + " vertices");
for (IrVertex vertex : polygon.vertices) {
System.out.println("\t" + vertex.pos);
}
}
/** /**
* Convert mesh from quads / triangles to triangles only. * Convert mesh from quads / triangles to triangles only.
*/ */
public static void triangulate(IrMesh mesh) { public static void triangulate(IrMesh mesh) {
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length); List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
EarClippingTriangulator triangulator = new EarClippingTriangulator();
for (IrPolygon inputPoly : mesh.polygons) { for (IrPolygon inputPoly : mesh.polygons) {
if (inputPoly.vertices.length == 4) { int numVertices = inputPoly.vertices.length;
if (numVertices < 3) {
// point / edge
logger.log(Level.WARNING, "Point or edge encountered. Ignoring.");
} else if (numVertices == 3) {
// triangle
newPolygons.add(inputPoly);
} else if (numVertices == 4) {
// quad
IrPolygon[] tris = quadToTri(inputPoly); IrPolygon[] tris = quadToTri(inputPoly);
newPolygons.add(tris[0]); newPolygons.add(tris[0]);
newPolygons.add(tris[1]); newPolygons.add(tris[1]);
} else if (inputPoly.vertices.length == 3) {
newPolygons.add(inputPoly);
} else { } else {
// N-gon. We have to ignore it.. // N-gon
logger.log(Level.WARNING, "N-gon encountered, ignoring. " dumpPoly(inputPoly);
+ "The mesh may not appear correctly. " IrPolygon[] tris = triangulator.triangulate(inputPoly);
+ "Triangulate your model prior to export."); for (IrPolygon tri : tris) {
newPolygons.add(tri);
}
} }
} }
mesh.polygons = new IrPolygon[newPolygons.size()]; mesh.polygons = new IrPolygon[newPolygons.size()];
@ -373,12 +391,11 @@ public final class IrUtils {
boneIndices.put((byte)0); boneIndices.put((byte)0);
boneWeights.put(0f); boneWeights.put(0f);
} }
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
} else { } else {
boneIndices.putInt(0); boneIndices.putInt(0);
boneWeights.put(0f).put(0f).put(0f).put(0f); boneWeights.put(0f).put(0f).put(0f).put(0f);
} }
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
} }
} }

@ -0,0 +1,241 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.triangulator;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.IrPolygon;
import com.jme3.scene.plugins.IrVertex;
import java.util.ArrayList;
/**
* Implemented according to
* <ul>
* <li>http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf</li>
* <li>http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html</li>
* </ul>
*/
public final class EarClippingTriangulator {
private static enum VertexType {
Convex,
Reflex,
Ear;
}
private final ArrayList<Integer> indices = new ArrayList<Integer>();
private final ArrayList<VertexType> types = new ArrayList<VertexType>();
private final ArrayList<Vector2f> positions = new ArrayList<Vector2f>();
public EarClippingTriangulator() {
}
private static int ccw(Vector2f p0, Vector2f p1, Vector2f p2) {
float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
if (result > 0) {
return 1;
} else if (result < 0) {
return -1;
} else {
return 0;
}
}
private static boolean pointInTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) {
float d = ((t1.y - t2.y) * (t0.x - t2.x) + (t2.x - t1.x) * (t0.y - t2.y));
float a = ((t1.y - t2.y) * (p.x - t2.x) + (t2.x - t1.x) * (p.y - t2.y)) / d;
float b = ((t2.y - t0.y) * (p.x - t2.x) + (t0.x - t2.x) * (p.y - t2.y)) / d;
float c = 1 - a - b;
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1;
}
private static Matrix3f normalToMatrix(Vector3f norm) {
Vector3f tang1 = norm.cross(Vector3f.UNIT_X);
if (tang1.lengthSquared() < FastMath.ZERO_TOLERANCE) {
tang1 = norm.cross(Vector3f.UNIT_Y);
}
tang1.normalizeLocal();
Vector3f tang2 = norm.cross(tang1).normalizeLocal();
return new Matrix3f(
tang1.x, tang1.y, tang1.z,
tang2.x, tang2.y, tang2.z,
norm.x, norm.y, norm.z);
}
private int prev(int index) {
if (index == 0) {
return indices.size() - 1;
} else {
return index - 1;
}
}
private int next(int index) {
if (index == indices.size() - 1) {
return 0;
} else {
return index + 1;
}
}
private VertexType calcType(int index) {
int prev = prev(index);
int next = next(index);
Vector2f p0 = positions.get(prev);
Vector2f p1 = positions.get(index);
Vector2f p2 = positions.get(next);
if (ccw(p0, p1, p2) <= 0) {
return VertexType.Reflex;
} else {
for (int i = 0; i < positions.size() - 3; i++) {
int testIndex = (index + 2 + i) % positions.size();
if (types.get(testIndex) != VertexType.Reflex) {
continue;
}
Vector2f p = positions.get(testIndex);
if (pointInTriangle(p0, p1, p2, p)) {
return VertexType.Convex;
}
}
return VertexType.Ear;
}
}
private void updateType(int index) {
if (types.get(index) == VertexType.Convex) {
return;
}
types.set(index, calcType(index));
}
private void loadVertices(IrVertex[] vertices) {
indices.ensureCapacity(vertices.length);
types.ensureCapacity(vertices.length);
positions.ensureCapacity(vertices.length);
Vector3f normal = FastMath.computeNormal(
vertices[0].pos,
vertices[1].pos,
vertices[2].pos);
Matrix3f transform = normalToMatrix(normal);
for (int i = 0; i < vertices.length; i++) {
Vector3f projected = transform.mult(vertices[i].pos);
indices.add(i);
positions.add(new Vector2f(projected.x, projected.y));
types.add(VertexType.Reflex);
}
for (int i = 0; i < vertices.length; i++) {
types.set(i, calcType(i));
}
}
private IrPolygon createTriangle(IrPolygon polygon, int prev, int index, int next) {
int p0 = indices.get(prev);
int p1 = indices.get(index);
int p2 = indices.get(next);
IrPolygon triangle = new IrPolygon();
triangle.vertices = new IrVertex[] {
polygon.vertices[p0],
polygon.vertices[p1],
polygon.vertices[p2],
};
return triangle;
}
/**
* Triangulates the given polygon.
*
* Five or more vertices are required, if less are given, an exception
* is thrown.
*
* @param polygon The polygon to triangulate.
* @return N - 2 triangles, where N is the number of vertices in the polygon.
*
* @throws IllegalArgumentException If the polygon has less than 5 vertices.
*/
public IrPolygon[] triangulate(IrPolygon polygon) {
if (polygon.vertices.length < 5) {
throw new IllegalArgumentException("Only polygons with 5 or more vertices are supported");
}
try {
int numTris = 0;
IrPolygon[] triangles = new IrPolygon[polygon.vertices.length - 2];
loadVertices(polygon.vertices);
int index = 0;
while (types.size() > 3) {
if (types.get(index) == VertexType.Ear) {
int prev = prev(index);
int next = next(index);
triangles[numTris++] = createTriangle(polygon, prev, index, next);
indices.remove(index);
types.remove(index);
positions.remove(index);
next = next(prev);
updateType(prev);
updateType(next);
index = next(next);
} else {
index = next(index);
}
}
if (types.size() == 3) {
triangles[numTris++] = createTriangle(polygon, 0, 1, 2);
}
if (numTris != triangles.length) {
throw new AssertionError("Triangulation failed to generate enough triangles");
}
return triangles;
} finally {
indices.clear();
positions.clear();
types.clear();
}
}
}

@ -0,0 +1,64 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.triangulator;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.IrPolygon;
import com.jme3.scene.plugins.IrVertex;
import junit.framework.TestCase;
public class TriangulatorTest extends TestCase {
public void testTriangulator() {
Vector3f[] dataSet = new Vector3f[]{
new Vector3f(0.75f, 0.3f, 1.2f),
new Vector3f(0.75f, 0.3f, 0.0f),
new Vector3f(0.75f, 0.17f, 0.0f),
new Vector3f(0.75000095f, 0.17f, 1.02f),
new Vector3f(0.75f, -0.17f, 1.02f),
new Vector3f(0.75f, -0.17f, 0.0f),
new Vector3f(0.75f, -0.3f, 0.0f),
new Vector3f(0.75f, -0.3f, 1.2f)
};
IrPolygon poly = new IrPolygon();
poly.vertices = new IrVertex[dataSet.length];
for (int i = 0; i < dataSet.length; i++) {
poly.vertices[i] = new IrVertex();
poly.vertices[i].pos = dataSet[i];
}
EarClippingTriangulator triangulator = new EarClippingTriangulator();
triangulator.triangulate(poly);
}
}

@ -14,6 +14,7 @@ include 'jme3-terrain'
include 'jme3-desktop' include 'jme3-desktop'
include 'jme3-blender' include 'jme3-blender'
include 'jme3-jogl' include 'jme3-jogl'
include 'jme3-jinput'
include 'jme3-lwjgl' include 'jme3-lwjgl'
include 'jme3-lwjgl3' include 'jme3-lwjgl3'

@ -1,35 +1,35 @@
/* /*
Version Info Examples Version Info Examples
===================== =====================
Nightly Build Snapshot Nightly Build Snapshot
* git tag: * git tag:
* Full Version: 3.1-5124 * Full Version: 3.1-5124
* POM Version: 3.1.0-SNAPSHOT * POM Version: 3.1.0-SNAPSHOT
* NBM Revision: 5124 * NBM Revision: 5124
* NBM UC Suffix: nightly/3.1/plugins * NBM UC Suffix: nightly/3.1/plugins
Nightly Build Snapshot (PBRIsComing branch) Nightly Build Snapshot (PBRIsComing branch)
* git tag: * git tag:
* Full Version: 3.1-PBRIsComing-5124 * Full Version: 3.1-PBRIsComing-5124
* POM Version: 3.1.0-PBRIsComing-SNAPSHOT * POM Version: 3.1.0-PBRIsComing-SNAPSHOT
* NBM Revision: 5124 * NBM Revision: 5124
* NBM UC Suffix: PBRIsComing-nightly/3.1/plugins * NBM UC Suffix: PBRIsComing-nightly/3.1/plugins
Alpha1 Release Alpha1 Release
* git tag: v3.1.0-alpha1 * git tag: v3.1.0-alpha1
* Full Version: 3.1-alpha1 * Full Version: 3.1-alpha1
* POM Version: 3.1.0-alpha1 * POM Version: 3.1.0-alpha1
* NBM Revision: 0 * NBM Revision: 0
* NBM UC Suffix: stable/3.1/plugins * NBM UC Suffix: stable/3.1/plugins
Final Release Final Release
* git tag: v3.1.0 * git tag: v3.1.0
* Full Version: 3.1 * Full Version: 3.1
* POM Version: 3.1.0 * POM Version: 3.1.0
* NBM Revision: 0 * NBM Revision: 0
* NBM UC Suffix: stable/3.1/plugins * NBM UC Suffix: stable/3.1/plugins
*/ */
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import org.ajoberstar.grgit.* import org.ajoberstar.grgit.*
@ -54,6 +54,7 @@ ext {
jmeFullVersion = "${jmeVersion}-UNKNOWN" jmeFullVersion = "${jmeVersion}-UNKNOWN"
jmePomVersion = "unknown" jmePomVersion = "unknown"
jmeNbmUcSuffix = "unknown" jmeNbmUcSuffix = "unknown"
jmeVersionTag = "unknown"
} }
def getReleaseInfo(String tag) { def getReleaseInfo(String tag) {
@ -122,7 +123,7 @@ task configureVersionInfo {
jmeShortGitHash = head.abbreviatedId jmeShortGitHash = head.abbreviatedId
jmeBranchName = grgit.branch.current.name jmeBranchName = grgit.branch.current.name
jmeGitTag = grgit.tag.list().find { it.commit == head } jmeGitTag = grgit.tag.list().find { it.commit == head }
if (jmeGitTag != null) { if (jmeGitTag != null) {
jmeGitTag = jmeGitTag.name jmeGitTag = jmeGitTag.name
} else { } else {
@ -135,6 +136,7 @@ task configureVersionInfo {
jmePomVersion = "${releaseInfo.mainVersion}${releaseInfo.releaseSuffix}" jmePomVersion = "${releaseInfo.mainVersion}${releaseInfo.releaseSuffix}"
jmeNbmRevision = "0" jmeNbmRevision = "0"
jmeNbmUcSuffix = "stable/${releaseInfo.baseVersion}/plugins" jmeNbmUcSuffix = "stable/${releaseInfo.baseVersion}/plugins"
jmeVersionTag = releaseInfo.releaseName ?: "";
} else { } else {
// SNAPSHOT // SNAPSHOT
jmeFullVersion = jmeMainVersion jmeFullVersion = jmeMainVersion
@ -142,7 +144,7 @@ task configureVersionInfo {
if (System.env.TRAVIS_BRANCH != null) { if (System.env.TRAVIS_BRANCH != null) {
jmeBranchName = System.env.TRAVIS_BRANCH jmeBranchName = System.env.TRAVIS_BRANCH
} }
if (System.env.TRAVIS_PULL_REQUEST != null && if (System.env.TRAVIS_PULL_REQUEST != null &&
System.env.TRAVIS_PULL_REQUEST != "false") { System.env.TRAVIS_PULL_REQUEST != "false") {
jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST
} }
@ -157,8 +159,9 @@ task configureVersionInfo {
jmeFullVersion += "-${jmeRevision}" jmeFullVersion += "-${jmeRevision}"
jmePomVersion += "-SNAPSHOT" jmePomVersion += "-SNAPSHOT"
jmeNbmRevision = jmeRevision jmeNbmRevision = jmeRevision
jmeVersionTag = "SNAPSHOT"
} }
logger.warn("Full Version: ${jmeFullVersion}") logger.warn("Full Version: ${jmeFullVersion}")
logger.warn("POM Version: ${jmePomVersion}") logger.warn("POM Version: ${jmePomVersion}")
logger.warn("NBM Revision: ${jmeNbmRevision}") logger.warn("NBM Revision: ${jmeNbmRevision}")
@ -168,4 +171,4 @@ task configureVersionInfo {
logger.warn("Failed to get repository info: " + ex.message + ". " + \ logger.warn("Failed to get repository info: " + ex.message + ". " + \
"Only partial build info will be generated.") "Only partial build info will be generated.")
} }
} }
Loading…
Cancel
Save