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. 4
      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. 166
      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. 398
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  15. 157
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  16. 25
      jme3-core/src/main/java/com/jme3/scene/Node.java
  17. 26
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  18. 15
      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. 10
      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. 76
      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. 173
      jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java
  32. 244
      jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java
  33. 26
      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. 52
      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. 74
      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. 3
      version.gradle

@ -2,13 +2,11 @@
jmeVersion = 3.1.0
# Version used for application and settings folder, no spaces!
jmeMainVersion = 3.1
# Version addition pre-alpha-svn, Stable, Beta
jmeVersionTag = SNAPSHOT
# Increment this each time jmeVersionTag changes but jmeVersion stays the same
jmeVersionTagID = 0
# specify if JavaDoc should be built
buildJavaDoc = true
buildJavaDoc = false
# specify if SDK and Native libraries get built
buildNativeProjects = false

@ -13,8 +13,22 @@ sourceSets {
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 << {
@ -34,6 +48,3 @@ task updateVersionPropertiesFile << {
}
compileJava.dependsOn(updateVersionPropertiesFile)
dependencies {
}

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

@ -34,6 +34,7 @@ package com.jme3.effect;
import com.jme3.material.RenderState;
import com.jme3.math.Matrix3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Mesh;
/**
@ -80,6 +81,6 @@ public abstract class ParticleMesh extends Mesh {
/**
* 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.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
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 int imagesX = 1;
@ -59,89 +68,80 @@ public class ParticlePointMesh extends ParticleMesh {
this.emitter = emitter;
// set positions
FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles);
//if the buffer is already set only update the data
VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
if (buf != null) {
buf.updateData(pb);
} else {
VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
pvb.setupData(Usage.Stream, 3, Format.Float, pb);
setBuffer(pvb);
}
// set colors
ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4);
buf = getBuffer(VertexBuffer.Type.Color);
if (buf != null) {
buf.updateData(cb);
ByteBuffer eb = BufferUtils.createByteBuffer(BYTES_PER_PARTICLE * numParticles);
VertexBuffer vb = getBuffer(VertexBuffer.Type.InterleavedData);
if (vb != null) {
vb.updateData(eb);
} else {
VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
cvb.setNormalized(true);
setBuffer(cvb);
vb = new VertexBuffer(VertexBuffer.Type.InterleavedData);
vb.setupData(Usage.Stream, 1, Format.Byte, eb);
setBuffer(vb);
}
// set sizes
FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles);
buf = getBuffer(VertexBuffer.Type.Size);
if (buf != null) {
buf.updateData(sb);
} else {
VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size);
svb.setupData(Usage.Stream, 1, Format.Float, sb);
setBuffer(svb);
}
// 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);
if (getBuffer(VertexBuffer.Type.Position) == null) {
VertexBuffer pb = new VertexBuffer(VertexBuffer.Type.Position);
pb.setupData(Usage.Stream, 3, Format.Float, eb);
pb.updateData(null);
pb.setOffset(0);
pb.setStride(BYTES_PER_PARTICLE);
setBuffer(pb);
VertexBuffer cb = new VertexBuffer(VertexBuffer.Type.Color);
cb.setupData(Usage.Stream, 4, Format.UnsignedByte, eb);
cb.updateData(null);
cb.setNormalized(true);
cb.setOffset(POS_SIZE);
cb.setStride(BYTES_PER_PARTICLE);
setBuffer(cb);
VertexBuffer sb = new VertexBuffer(VertexBuffer.Type.Size);
sb.setupData(Usage.Stream, 1, Format.Float, eb);
sb.updateData(null);
sb.setOffset(POS_SIZE + COLOR_SIZE);
sb.setStride(BYTES_PER_PARTICLE);
setBuffer(sb);
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);
}
updateCounts();
}
@Override
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
FloatBuffer positions = (FloatBuffer) pvb.getData();
VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
ByteBuffer colors = (ByteBuffer) cvb.getData();
public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
VertexBuffer eb = getBuffer(VertexBuffer.Type.InterleavedData);
ByteBuffer elements = (ByteBuffer) eb.getData();
FloatBuffer floatElements = elements.asFloatBuffer();
VertexBuffer svb = getBuffer(VertexBuffer.Type.Size);
FloatBuffer sizes = (FloatBuffer) svb.getData();
float sizeScale = emitter.getWorldScale().x;
VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
FloatBuffer texcoords = (FloatBuffer) tvb.getData();
TempVars vars = TempVars.get();
try {
float[] floatArray = vars.skinTangents;
float sizeScale = emitter.getWorldScale().x;
int particlesPerIteration = floatArray.length / FLOATS_PER_PARTICLE;
int iterations = (particles.length + particlesPerIteration - 1) / particlesPerIteration;
// update data in vertex buffers
positions.rewind();
colors.rewind();
sizes.rewind();
texcoords.rewind();
for (int i = 0; i < particles.length; i++){
Particle p = particles[i];
int particleIndex = 0;
for (int iteration = 0; iteration < iterations; iteration++) {
int particlesRemaining = Math.min(
particles.length - particleIndex,
particlesPerIteration);
positions.put(p.position.x)
.put(p.position.y)
.put(p.position.z);
int floatIndex = 0;
for (int i = 0; i < particlesRemaining; i++) {
Particle p = particles[particleIndex++];
sizes.put(p.size * sizeScale);
colors.putInt(p.color.asIntABGR());
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;
@ -151,17 +151,25 @@ public class ParticlePointMesh extends ParticleMesh {
float endX = startX + (1f / imagesX);
float endY = startY + (1f / imagesY);
texcoords.put(startX).put(startY).put(endX).put(endY);
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);
// cheating!
rm.getRenderer().updateBufferData(eb);
} finally {
vars.release();
}
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.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
@ -145,7 +146,7 @@ public class ParticleTriMesh extends ParticleMesh {
}
@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);
// comparator.setCamera(cam);
// Arrays.sort(particlesCopy, comparator);

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

@ -349,7 +349,12 @@ public enum Caps {
/**
* 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

@ -284,6 +284,12 @@ public interface Renderer {
*/
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.
* The state of the native objects is reset in such way, that using

@ -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 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_RGBA_S3TC_DXT1_EXT = 0x83F1;
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);
}
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;
}
}

@ -64,6 +64,7 @@ import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@ -100,6 +101,7 @@ public final class GLRenderer implements Renderer {
private final GLExt glext;
private final GLFbo glfbo;
private final TextureUtil texUtil;
private final AsyncFrameReader frameReader;
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
@ -109,6 +111,7 @@ public final class GLRenderer implements Renderer {
this.glfbo = glfbo;
this.glext = glext;
this.texUtil = new TextureUtil(gl, gl2, glext);
this.frameReader = new AsyncFrameReader(this, gl, glext, context);
}
@Override
@ -349,6 +352,10 @@ public final class GLRenderer implements Renderer {
caps.add(Caps.TextureCompressionETC1);
}
if (hasExtension("GL_ARB_texture_compression_rgtc")) {
caps.add(Caps.TextureCompressionRGTC);
}
// == end texture format extensions ==
if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) {
@ -474,7 +481,6 @@ public final class GLRenderer implements Renderer {
{
sb.append("\t").append(cap.toString()).append("\n");
}
sb.append("\nHardware limits: \n");
for (Limits limit : Limits.values()) {
Integer value = limits.get(limit);
@ -525,9 +531,9 @@ public final class GLRenderer implements Renderer {
if (caps.contains(Caps.CoreProfile)) {
// Core Profile requires VAO to be bound.
gl3.glGenVertexArrays(intBuf16);
int vaoId = intBuf16.get(0);
gl3.glBindVertexArray(vaoId);
// gl3.glGenVertexArrays(intBuf16);
// int vaoId = intBuf16.get(0);
// gl3.glBindVertexArray(vaoId);
}
if (gl2 != null) {
gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
@ -883,6 +889,7 @@ public final class GLRenderer implements Renderer {
public void postFrame() {
objManager.deleteUnused(this);
frameReader.updateReadRequests();
gl.resetStats();
}
@ -1524,15 +1531,15 @@ public final class GLRenderer implements Renderer {
bindFrameBuffer(fb);
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
updateFrameBufferAttachment(fb, colorBuf);
}
FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
if (depthBuf != null) {
updateFrameBufferAttachment(fb, depthBuf);
}
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
updateFrameBufferAttachment(fb, colorBuf);
}
setReadDrawBuffers(fb);
checkFrameBufferError();
@ -1697,11 +1704,11 @@ public final class GLRenderer implements Renderer {
}
}
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
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) {
RenderBuffer rb = fb.getColorBuffer();
if (rb == null) {
@ -1720,12 +1727,30 @@ public final class GLRenderer implements Renderer {
setFrameBuffer(null);
}
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) {
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) {
@ -2346,32 +2371,37 @@ public final class GLRenderer implements Renderer {
context.attribIndexList.copyNewToOld();
}
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");
}
if (context.boundShaderProgram <= 0) {
throw new IllegalStateException("Cannot render mesh without shader bound");
}
Attribute attrib = context.boundShader.getAttribute(vb.getBufferType());
private int updateAttributeLocation(Shader shader, VertexBuffer.Type attribType) {
Attribute attrib = shader.getAttribute(attribType);
int loc = attrib.getLocation();
if (loc == -1) {
return; // not defined
return -1; // not defined
}
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
// the internal name of the enum (Position).
if (loc < 0) {
attrib.setLocation(-1);
return; // not available in shader.
return -1; // not available in shader.
} else {
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 (!caps.contains(Caps.MeshInstancing)) {
@ -2395,11 +2425,11 @@ public final class GLRenderer implements Renderer {
VertexBuffer[] attribs = context.boundAttribs;
for (int i = 0; i < slotsRequired; i++) {
if (!context.attribIndexList.moveToNew(loc + i)) {
gl.glEnableVertexAttribArray(loc + i);
if (!context.attribIndexList.moveToNew(location + i)) {
gl.glEnableVertexAttribArray(location + i);
}
}
if (attribs[loc] != vb) {
if (attribs[location] != vb) {
// NOTE: Use id from interleaved buffer if specified
int bufId = idb != null ? idb.getId() : vb.getId();
assert bufId != -1;
@ -2412,7 +2442,7 @@ public final class GLRenderer implements Renderer {
}
if (slotsRequired == 1) {
gl.glVertexAttribPointer(loc,
gl.glVertexAttribPointer(location,
vb.getNumComponents(),
convertFormat(vb.getFormat()),
vb.isNormalized(),
@ -2428,7 +2458,7 @@ public final class GLRenderer implements Renderer {
// 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(loc + i,
gl.glVertexAttribPointer(location + i,
4,
convertFormat(vb.getFormat()),
vb.isNormalized(),
@ -2438,7 +2468,7 @@ public final class GLRenderer implements Renderer {
}
for (int i = 0; i < slotsRequired; i++) {
int slot = loc + i;
int slot = location + i;
if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
// non-instanced -> instanced
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) {
setVertexAttrib(vb, null);
}
public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
if (useInstancing) {
if (count > 1) {
glext.glDrawArraysInstancedARB(convertElementMode(mode), 0,
vertCount, count);
} else {
@ -2503,43 +2617,6 @@ public final class GLRenderer implements Renderer {
int vertCount = mesh.getVertexCount();
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
if (mesh.getMode() == Mode.Hybrid) {
int[] modeStart = mesh.getModeStart();
int[] elementLengths = mesh.getElementLengths();
int elMode = convertElementMode(Mode.Triangles);
int fmt = convertFormat(indexBuf.getFormat());
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 {
if (useInstancing) {
glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
indexBuf.getData().limit(),
@ -2555,7 +2632,6 @@ public final class GLRenderer implements Renderer {
0);
}
}
}
/*********************************************************************\
|* Render Calls *|
@ -2583,27 +2659,16 @@ public final class GLRenderer implements Renderer {
}
}
public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
int id = mesh.getId();
if (id == -1) {
IntBuffer temp = intBuf1;
gl3.glGenVertexArrays(temp);
id = temp.get(0);
mesh.setId(id);
}
if (context.boundVertexArray != id) {
gl3.glBindVertexArray(id);
context.boundVertexArray = id;
}
private void setupVertexBuffersLegacy(Mesh mesh, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
if (instanceData != null) {
setVertexAttrib(instanceData, null);
for (VertexBuffer vb : instanceData) {
setVertexAttrib(vb, null);
}
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
@ -2623,75 +2688,134 @@ public final class GLRenderer implements Renderer {
}
}
private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
if (mesh.getId() == -1) {
updateVertexArray(mesh, instanceData);
} else {
// TODO: Check if it was updated
private void setupVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
if (context.boundVertexArray != mesh.getId()) {
gl3.glBindVertexArray(mesh.getId());
context.boundVertexArray = mesh.getId();
if (instanceData != null) {
for (VertexBuffer vb : instanceData) {
setVertexAttribVAO(vb, null);
}
}
// IntMap<VertexBuffer> buffers = mesh.getBuffers();
VertexBuffer indices;
if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod);
} else {
indices = mesh.getBuffer(Type.Index);
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 (indices != null) {
drawTriangleList(indices, mesh, count);
if (vb.getStride() == 0) {
// not interleaved
setVertexAttribVAO(vb, null);
} else {
drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
// interleaved
setVertexAttribVAO(vb, interleavedData);
}
clearVertexAttribs();
}
mesh.clearUpdateNeeded();
}
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
private void updateVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
// Here while count is still passed in. Can be removed when/if
// the method is collapsed again. -pspeed
count = Math.max(mesh.getInstanceCount(), count);
if (instanceData != null) {
for (VertexBuffer vb : instanceData) {
if (vb.isUpdateNeeded()) {
updateBufferData(vb);
}
}
}
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
if (interleavedData != null) {
if (interleavedData.isUpdateNeeded()) {
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);
}
}
private VertexBuffer getIndexBuffer(Mesh mesh, int lod) {
VertexBuffer indices;
if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod);
} else {
indices = mesh.getBuffer(Type.Index);
}
return indices;
}
if (instanceData != null) {
for (VertexBuffer vb : instanceData) {
setVertexAttrib(vb, null);
private void setVertexArrayObject(Mesh mesh) {
int id = mesh.getId();
if (id == -1) {
IntBuffer temp = intBuf1;
gl3.glGenVertexArrays(temp);
id = temp.get(0);
mesh.setId(id);
objManager.registerObject(mesh);
}
if (context.boundVertexArray != id) {
gl3.glBindVertexArray(id);
context.boundVertexArray = id;
}
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|| vb.getBufferType() == Type.Index) {
continue;
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
setVertexArrayObject(mesh);
// 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();
}
}
if (vb.getStride() == 0) {
// not interleaved
setVertexAttrib(vb);
if (indices != null) {
if (indices.isUpdateNeeded()) {
updateBufferData(indices);
}
drawTriangleList(indices, mesh, count);
context.boundElementArrayVBO = 0;
} else {
// interleaved
setVertexAttrib(vb, interleavedData);
drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
}
}
clearVertexAttribs();
private void renderMeshLegacy(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
setupVertexBuffersLegacy(mesh, instanceData);
VertexBuffer indices = getIndexBuffer(mesh, lod);
clearVertexAttribs();
if (indices != null) {
drawTriangleList(indices, mesh, count);
} else {
@ -2707,7 +2831,7 @@ public final class GLRenderer implements Renderer {
if (count > 1 && !caps.contains(Caps.MeshInstancing)) {
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()) {
gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth();
@ -2717,11 +2841,27 @@ public final class GLRenderer implements Renderer {
gl4.glPatchParameter(mesh.getPatchVertexCount());
}
statistics.onMeshDrawn(mesh, lod, count);
// if (ctxCaps.GL_ARB_vertex_array_object){
// renderMeshVertexArray(mesh, lod, count);
// }else{
// Here while count is still passed in. Can be removed when/if
// the method is collapsed again. -pspeed
count = Math.max(mesh.getInstanceCount(), count);
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) {

@ -43,6 +43,7 @@ import com.jme3.math.Matrix4f;
import com.jme3.math.Triangle;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Renderer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
@ -50,6 +51,7 @@ import com.jme3.scene.mesh.*;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.NativeObject;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
@ -74,7 +76,7 @@ import java.util.ArrayList;
*
* @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
@ -130,14 +132,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
TriangleFan(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),
Reserved(false),
/**
* Used for Tesselation only. Requires to set the number of vertices
* 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 maxNumWeights = -1; // only if using skeletal animation
private int[] elementLengths;
private int[] modeStart;
private Mode mode = Mode.Triangles;
/**
@ -196,6 +189,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
public Mesh(){
}
protected Mesh(int id) {
super(id);
}
/**
* Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
* buffers} are shared between this and the clone mesh, the rest
@ -205,23 +202,12 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
@Override
public Mesh clone() {
try {
Mesh clone = (Mesh) super.clone();
clone.meshBound = meshBound.clone();
clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.buffers = buffers.clone();
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
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,7 +218,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @return a deep clone of this mesh.
*/
public Mesh deepClone(){
try{
Mesh clone = (Mesh) super.clone();
clone.meshBound = meshBound != null ? meshBound.clone() : null;
@ -248,7 +233,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
clone.buffersList.add(bufClone);
}
clone.vertexArrayID = -1;
clone.vertCount = vertCount;
clone.elementCount = elementCount;
clone.instanceCount = instanceCount;
@ -257,12 +241,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
// if the bone weight/index buffers are modified
clone.maxNumWeights = maxNumWeights;
clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
clone.modeStart = modeStart != null ? modeStart.clone() : null;
return clone;
}catch (CloneNotSupportedException ex){
throw new AssertionError();
}
}
/**
@ -306,13 +285,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
@Override
public Mesh jmeClone() {
try {
Mesh clone = (Mesh)super.clone();
clone.vertexArrayID = -1;
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
return (Mesh) super.clone();
}
/**
@ -328,8 +301,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
this.buffersList = cloner.clone(buffersList);
this.buffers = cloner.clone(buffers);
this.lodLevels = cloner.clone(lodLevels);
this.elementLengths = cloner.clone(elementLengths);
this.modeStart = cloner.clone(modeStart);
}
/**
@ -408,11 +379,16 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
// convert weights on the heap
VertexBuffer weights = getBuffer(Type.BoneWeight);
if (!weights.getData().hasArray()) {
if (weights.getFormat() == Format.Float) {
FloatBuffer originalWeight = (FloatBuffer) weights.getData();
FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
originalWeight.clear();
arrayWeight.put(originalWeight);
weights.updateData(arrayWeight);
} else {
// UByte to Float conversion
throw new UnsupportedOperationException("Not yet supported");
}
}
weights.setUsage(Usage.CpuOnly);
// position, normal, and tanget buffers to be in "Stream" mode
@ -474,6 +450,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
tangents.setUpdateNeeded();
}
}
this.setUpdateNeeded();
}
/**
@ -509,40 +487,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
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
*
@ -803,14 +747,22 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* {@link #setInterleaved() interleaved} format.
*/
public void updateCounts(){
if (getBuffer(Type.InterleavedData) != null)
throw new IllegalStateException("Should update counts before interleave");
// if (getBuffer(Type.InterleavedData) != null) {
// throw new IllegalStateException("Should update counts before interleave");
// }
VertexBuffer pb = getBuffer(Type.Position);
VertexBuffer ib = getBuffer(Type.Index);
if (pb != null) {
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){
elementCount = computeNumElements(ib.getData().limit());
}else{
@ -940,23 +892,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
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.
* 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
*/
public IndexBuffer getIndicesAsList(){
if (mode == Mode.Hybrid)
throw new UnsupportedOperationException("Hybrid mode not supported");
IndexBuffer ib = getIndexBuffer();
if (ib != null){
if (mode.isListMode()){
@ -1426,16 +1358,30 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
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 {
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(vertCount, "vertCount", -1);
out.write(elementCount, "elementCount", -1);
@ -1443,8 +1389,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
out.write(maxNumWeights, "max_num_weights", -1);
out.write(mode, "mode", Mode.Triangles);
out.write(collisionTree, "collisionTree", null);
out.write(elementLengths, "elementLengths", null);
out.write(modeStart, "modeStart", null);
out.write(pointSize, "pointSize", 1f);
//Removing HW skinning buffers to not save them
@ -1480,14 +1424,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
instanceCount = in.readInt("instanceCount", -1);
maxNumWeights = in.readInt("max_num_weights", -1);
mode = in.readEnum("mode", Mode.class, Mode.Triangles);
elementLengths = in.readIntArray("elementLengths", null);
modeStart = in.readIntArray("modeStart", null);
collisionTree = (BIHTree) in.readSavable("collisionTree", null);
elementLengths = in.readIntArray("elementLengths", null);
modeStart = in.readIntArray("modeStart", null);
pointSize = in.readFloat("pointSize", 1f);
// in.readStringSavableMap("buffers", null);
buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
for (Entry<VertexBuffer> entry : buffers){
buffersList.add(entry.getValue());

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

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

@ -524,6 +524,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
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
* 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
* {@link Format format} and would be able to contain the given number
* 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){
if (components < 1 || components > 4)

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

@ -303,6 +303,16 @@ public final class Shader extends NativeObject {
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(){
return uniforms;
}

@ -69,7 +69,6 @@ public abstract class ShaderGenerator {
protected ShaderGenerator(AssetManager assetManager) {
this.assetManager = assetManager;
}
public void initialize(TechniqueDef techniqueDef) {
this.techniqueDef = techniqueDef;
}
@ -77,6 +76,8 @@ public abstract class ShaderGenerator {
/**
* Generate vertex and fragment shaders for the given technique
*
* @param definesSourceCode Defines to include alongside the shader. May be
* null.
* @return a Shader program
*/
public Shader generateShader(String definesSourceCode) {
@ -114,14 +115,13 @@ public abstract class ShaderGenerator {
* @return the code of the generated vertex shader
*/
protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
if (type == ShaderType.TessellationControl ||
type == ShaderType.TessellationEvaluation ||
type == ShaderType.Geometry) {
if (type == ShaderType.TessellationControl
|| type == ShaderType.TessellationEvaluation
|| type == ShaderType.Geometry) {
// TODO: Those are not supported.
// Too much code assumes that type is either Vertex or Fragment
return null;
}
indent = 0;
StringBuilder sourceDeclaration = new StringBuilder();

@ -164,4 +164,7 @@ public class NullRenderer implements Renderer {
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}.
*/
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 boolean isDepth;

@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable {
OBJTYPE_SHADERSOURCE = 5,
OBJTYPE_AUDIOBUFFER = 6,
OBJTYPE_AUDIOSTREAM = 7,
OBJTYPE_FILTER = 8;
OBJTYPE_FILTER = 8,
OBJTYPE_MESH = 9;
/**
* 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.blender.BlenderModelLoader : blend
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.SceneWithAnimationLoader : fba
LOADER com.jme3.scene.plugins.fbx.FbxLoader : fbx
# 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_DXT3 = 0x33545844;
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_ATI2 = 0x32495441; // 0x41544932;
private static final int PF_DX10 = 0x30315844; // a DX10 format
@ -94,6 +96,9 @@ public class DDSLoader implements AssetLoader {
DX10DIM_TEXTURE3D = 0x4;
private static final int DX10MISC_GENERATE_MIPS = 0x1,
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 int width;
private int height;
@ -105,9 +110,11 @@ public class DDSLoader implements AssetLoader {
private int caps2;
private boolean directx10;
private boolean compressed;
private boolean dxtOrRgtc;
private boolean texture3D;
private boolean grayscaleOrAlpha;
private boolean normal;
private ColorSpace colorSpace;
private Format pixelFormat;
private int bpp;
private int[] sizes;
@ -133,7 +140,8 @@ public class DDSLoader implements AssetLoader {
((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap);
}
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 {
if (stream != null){
stream.close();
@ -145,18 +153,24 @@ public class DDSLoader implements AssetLoader {
in = new LittleEndien(stream);
loadHeader();
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 {
int dxgiFormat = in.readInt();
if (dxgiFormat == 0) {
pixelFormat = Format.ETC1;
compressed = true;
bpp = 4;
} else {
pixelFormat = null; // DXGIFormat.getJmeFormat(dxgiFormat);
if (pixelFormat == null) {
throw new IOException("Unsupported DX10 format: " + dxgiFormat);
}
compressed = true;
bpp = pixelFormat.getBitsPerPixel();
compressed = pixelFormat.isCompressed();
}
int resDim = in.readInt();
if (resDim == DX10DIM_TEXTURE3D) {
@ -201,6 +215,7 @@ public class DDSLoader implements AssetLoader {
caps2 = in.readInt();
in.skipBytes(12);
texture3D = false;
colorSpace = ColorSpace.sRGB;
if (!directx10) {
if (!is(caps1, DDSCAPS_TEXTURE)) {
@ -268,10 +283,12 @@ public class DDSLoader implements AssetLoader {
} else {
pixelFormat = Image.Format.DXT1;
}
dxtOrRgtc = true;
break;
case PF_DXT3:
bpp = 8;
pixelFormat = Image.Format.DXT3;
dxtOrRgtc = true;
break;
case PF_DXT5:
bpp = 8;
@ -279,17 +296,24 @@ public class DDSLoader implements AssetLoader {
if (swizzle == SWIZZLE_xGxR) {
normal = true;
}
dxtOrRgtc = true;
break;
/*
case PF_ATI1:
bpp = 4;
pixelFormat = Image.Format.LTC;
pixelFormat = Image.Format.RTC;
dxtOrRgtc = true;
break;
case PF_ATI2:
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;
*/
case PF_DX10:
compressed = false;
directx10 = true;
@ -530,6 +554,30 @@ public class DDSLoader implements AssetLoader {
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
*
@ -738,8 +786,10 @@ public class DDSLoader implements AssetLoader {
ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
if (depth > 1 && !texture3D) {
for (int i = 0; i < depth; i++) {
if (compressed) {
if (compressed && dxtOrRgtc) {
allMaps.add(readDXT2D(flip, totalSize));
} else if (compressed) {
allMaps.add(readCompressed2Dor3D(flip, totalSize));
} else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale2D(flip, totalSize));
} else {
@ -747,8 +797,10 @@ public class DDSLoader implements AssetLoader {
}
}
} else if (texture3D) {
if (compressed) {
if (compressed && dxtOrRgtc) {
allMaps.add(readDXT3D(flip, totalSize));
} else if (compressed) {
allMaps.add(readCompressed2Dor3D(flip, totalSize));
} else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale3D(flip, totalSize));
} else {
@ -756,8 +808,10 @@ public class DDSLoader implements AssetLoader {
}
} else {
if (compressed) {
if (compressed && dxtOrRgtc) {
allMaps.add(readDXT2D(flip, totalSize));
} else if (compressed) {
allMaps.add(readCompressed2Dor3D(flip, totalSize));
} else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale2D(flip, totalSize));
} else {
@ -822,7 +876,7 @@ public class DDSLoader implements AssetLoader {
buf.append((char) (value & 0xFF));
buf.append((char) ((value & 0xFF00) >> 8));
buf.append((char) ((value & 0xFF0000) >> 16));
buf.append((char) ((value & 0xFF00000) >> 24));
buf.append((char) ((value & 0xFF000000) >> 24));
return buf.toString();
}

@ -213,20 +213,18 @@ public class DXTFlipper {
case DXT5:
type = 3;
break;
/*
case LATC:
case RGTC:
type = 4;
break;
case LTC:
case RTC:
type = 5;
break;
*/
default:
throw new IllegalArgumentException();
}
// 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;
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 {
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.URL;
import java.net.URLConnection;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -44,9 +46,9 @@ import java.util.logging.Logger;
/**
* Utility class to register, extract, and load native libraries.
* <br>
* Register your own libraries via the {@link #registerNativeLibrary(String, Platform, String, String)} method, for
* each platform.
* You can then extract this library (depending on platform), by
* Register your own libraries via the
* {@link #registerNativeLibrary(String, Platform, String, String)} method, for
* each platform. You can then extract this library (depending on platform), by
* using {@link #loadNativeLibrary(java.lang.String, boolean) }.
* <br>
* Example:<br>
@ -62,8 +64,8 @@ import java.util.logging.Logger;
* This will register the library. Load it via: <br>
* <code><pre>
* NativeLibraryLoader.loadNativeLibrary("mystuff", true);
* </pre></code>
* It will load the right library automatically based on the platform.
* </pre></code> It will load the right library automatically based on the
* platform.
*
* @author Kirill Vainer
*/
@ -81,16 +83,17 @@ public final class NativeLibraryLoader {
* Register a new known library.
*
* 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
* been specified for.
* @param path The path inside the natives-jar or classpath
* corresponding to this library. Must be compatible with the platform
* argument.
* @param extractAsName The filename that the library should be extracted as,
* if null, use the same name as in the path.
* @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 been
* specified for.
* @param path The path inside the natives-jar or classpath corresponding to
* this library. Must be compatible with the platform argument.
* @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,
String path, String extractAsName) {
@ -102,17 +105,18 @@ public final class NativeLibraryLoader {
* Register a new known JNI library.
*
* 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
* been specified for.
* @param path The path inside the natives-jar or classpath
* corresponding to this library. Must be compatible with the platform
* argument.
* @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 been
* specified for.
* @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,
String path) {
@ -202,9 +206,9 @@ public final class NativeLibraryLoader {
/**
* Determine if native bullet is on the classpath.
*
* Currently the context extracts the native bullet libraries, so
* this method is needed to determine if it is needed.
* Ideally, native bullet should be responsible for its own natives.
* Currently the context extracts the native bullet libraries, so this
* method is needed to determine if it is needed. Ideally, native bullet
* should be responsible for its own natives.
*
* @return True native bullet is on the classpath, false otherwise.
*/
@ -218,32 +222,34 @@ public final class NativeLibraryLoader {
}
/**
* Specify a custom location where native libraries should
* be extracted to. Ensure this is a unique path not used
* by other applications to extract their libraries.
* Set to <code>null</code> to restore default
* Specify a custom location where native libraries should be extracted to.
* Ensure this is a unique path not used by other applications to extract
* their libraries. Set to <code>null</code> to restore default
* functionality.
*
* @param path Path where to extract native libraries.
*/
public static void setCustomExtractionFolder(String path) {
if (path != null) {
extractionFolderOverride = new File(path).getAbsoluteFile();
} else {
extractionFolderOverride = null;
}
}
/**
* Returns the folder where native libraries will be extracted.
* This is automatically determined at run-time based on the
* following criteria:<br>
* Returns the folder where native libraries will be extracted. This is
* automatically determined at run-time based on the following criteria:<br>
* <ul>
* <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
* extraction folder} has been specified, it is returned.
* <li>If the user can write to the working folder, then it
* is returned.</li>
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder}
* is used, to prevent collisions, a special subfolder is used
* called <code>natives_&lt;hash&gt;</code> where &lt;hash&gt;
* is computed automatically as the XOR of the classpath hash code
* and the last modified date of this class.
* <li>If the user can write to the working folder, then it is
* returned.</li>
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} is
* used, to prevent collisions, a special subfolder is used called
* <code>natives_&lt;hash&gt;</code> where &lt;hash&gt; is computed
* automatically as the XOR of the classpath hash code and the last modified
* date of this class.
*
* @return Path where natives will be extracted to.
*/
@ -272,9 +278,9 @@ public final class NativeLibraryLoader {
/**
* 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
* particular version of the OS does not have a dedicated cache folder,
* hence, we use the user's home folder instead as the root.
* If the OS cache folder is missing, the assumption is that this particular
* version of the OS does not have a dedicated cache folder, hence, we use
* the user's home folder instead as the root.
*
* The folder returned is as follows:<br>
* <ul>
@ -360,7 +366,8 @@ public final class NativeLibraryLoader {
try {
conn.getInputStream().close();
conn.getOutputStream().close();
} catch (IOException ex) { }
} catch (IOException ex) {
}
}
}
}
@ -401,8 +408,8 @@ public final class NativeLibraryLoader {
}
/**
* Removes platform-specific portions of a library file name so
* that it can be accepted by {@link System#loadLibrary(java.lang.String) }.
* Removes platform-specific portions of a library file name so that it can
* be accepted by {@link System#loadLibrary(java.lang.String) }.
* <p>
* E.g.<br>
* <ul>
@ -528,10 +535,24 @@ public final class NativeLibraryLoader {
* First extracts the native library and then 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 isRequired If true and the library fails to load, throw exception.
* If false, do nothing if it fails to load.
*/
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()) {
throw new UnsupportedOperationException("JVM is running under "
+ "reduced permissions. Cannot load native libraries.");
@ -547,8 +568,8 @@ public final class NativeLibraryLoader {
"The required native library '" + name + "'"
+ " is not available for your OS: " + platform);
} else {
logger.log(Level.FINE, "The optional native library ''{0}''" +
" is not available for your OS: {1}",
logger.log(Level.FINE, "The optional native library ''{0}''"
+ " is not available for your OS: {1}",
new Object[]{name, platform});
return;
}
@ -578,15 +599,15 @@ public final class NativeLibraryLoader {
if (url == null) {
// Attempt to load it as a system library.
// Need to unmap it from library specific parts.
String unmappedName = unmapLibraryName(fileNameInJar);
try {
// XXX: HACK. Vary loading method based on library name..
// lwjgl and jinput handle loading by themselves.
if (!name.equals("lwjgl") && !name.equals("jinput")) {
// Need to unmap it from library specific parts.
if (loadLibrary) {
System.loadLibrary(unmappedName);
logger.log(Level.FINE, "Loaded system installed "
+ "version of native library: {0}", unmappedName);
} else {
throw new UnsatisfiedLinkError();
}
} catch (UnsatisfiedLinkError e) {
if (isRequired) {
@ -595,9 +616,9 @@ public final class NativeLibraryLoader {
+ " was not found in the classpath via '" + pathInJar
+ "'. Error message: " + e.getMessage());
} else {
logger.log(Level.FINE, "The optional native library ''{0}''" +
" was not found in the classpath via ''{1}''" +
". Error message: {2}",
logger.log(Level.FINE, "The optional native library ''{0}''"
+ " was not found in the classpath via ''{1}''"
+ ". Error message: {2}",
new Object[]{unmappedName, pathInJar, e.getMessage()});
}
}
@ -624,12 +645,12 @@ public final class NativeLibraryLoader {
in = conn.getInputStream();
} catch (IOException ex) {
// Maybe put more detail here? Not sure..
throw new UnsatisfiedLinkError("Failed to open file: '" + url +
"'. Error: " + ex);
throw new UnsatisfiedLinkError("Failed to open file: '" + url
+ "'. Error: " + ex);
}
File targetFile = new File(extactionDirectory, loadedAsFileName);
OutputStream out = null;
FileOutputStream out = null;
try {
if (targetFile.exists()) {
// OK, compare last modified date of this file to
@ -638,15 +659,17 @@ public final class NativeLibraryLoader {
long sourceLastModified = conn.getLastModified();
// Allow ~1 second range for OSes that only support low precision
if (targetLastModified + 1000 > sourceLastModified) {
logger.log(Level.FINE, "Not copying library {0}. " +
"Latest already extracted.",
if (Math.abs(targetLastModified - sourceLastModified) < 1000) {
logger.log(Level.FINE, "Not copying library {0}. "
+ "Identical version already extracted.",
loadedAsFileName);
return;
}
}
out = new FileOutputStream(targetFile);
FileLock lock = out.getChannel().lock();
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
@ -661,6 +684,8 @@ public final class NativeLibraryLoader {
// this will cause the last modified date to be lower than
// date created which makes no sense
targetFile.setLastModified(conn.getLastModified());
} catch (OverlappingFileLockException ex) {
// do nothing with ex
} catch (IOException ex) {
if (ex.getMessage().contains("used by another process")) {
return;
@ -669,25 +694,21 @@ public final class NativeLibraryLoader {
+ "library to: " + targetFile);
}
} finally {
// XXX: HACK. Vary loading method based on library name..
// 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.
if (loadLibrary) {
System.load(targetFile.getAbsolutePath());
}
if (in != null) {
try { in.close(); } catch (IOException ex) { }
try {
in.close();
} catch (IOException ex) {
}
}
if (out != null) {
try { out.close(); } catch (IOException ex) { }
try {
out.close();
} catch (IOException ex) {
}
}
}

@ -34,6 +34,7 @@ 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.Format;
@ -47,20 +48,19 @@ import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
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;
public class AwtPanel extends Canvas implements SceneProcessor {
public class AwtPanel extends Canvas implements JmePanel, SceneProcessor {
private boolean attachAsMain = false;
private BufferedImage img;
private FrameBuffer fb;
private boolean srgb = false;
private ByteBuffer byteBuf;
private IntBuffer intBuf;
// private FrameBuffer fb;
private RenderManager rm;
private PaintMode paintMode;
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
@ -75,23 +75,37 @@ public class AwtPanel extends Canvas implements SceneProcessor {
// Reshape vars
private int newWidth = 1;
private int newHeight = 1;
private AtomicBoolean reshapeNeeded = new AtomicBoolean(false);
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
private final Object lock = new Object();
public AwtPanel(PaintMode paintMode){
this(paintMode, false);
// Buffer pool and pending buffers
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){
this.paintMode = paintMode;
this.srgb = srgb;
invalidatePendingFrames();
if (paintMode == PaintMode.Accelerated){
setIgnoreRepaint(true);
}
addComponentListener(new ComponentAdapter(){
@Override
public void componentResized(ComponentEvent e) {
addComponentListener(resizeListener);
}
public void onResize(ComponentEvent e) {
synchronized (lock) {
int newWidth2 = Math.max(getWidth(), 1);
int newHeight2 = Math.max(getHeight(), 1);
@ -103,8 +117,6 @@ public class AwtPanel extends Canvas implements SceneProcessor {
}
}
}
});
}
@Override
public void addNotify(){
@ -128,13 +140,13 @@ public class AwtPanel extends Canvas implements SceneProcessor {
super.removeNotify();
}
@Override
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g;
synchronized (lock){
g2d.drawImage(img, transformOp, 0, 0);
}
}
// @Override
// public void paint(Graphics g){
// Graphics2D g2d = (Graphics2D) g;
// synchronized (lock){
// g2d.drawImage(img, transformOp, 0, 0);
// }
// }
public boolean checkVisibilityState(){
if (!hasNativePeer.get()){
@ -157,24 +169,85 @@ public class AwtPanel extends Canvas implements SceneProcessor {
return currentShowing;
}
public void repaintInThread(){
// Convert screenshot.
byteBuf.clear();
rm.getRenderer().readFrameBuffer(fb, byteBuf);
// public void repaintInThread(){
// // Convert screenshot.
// byteBuf.clear();
// 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();
// }
// }
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;
}
try {
ByteBuffer nextFrame = null;
// while (!pendingFrames.isEmpty() && pendingFrames.peek().isDone()) {
// 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(){
// Convert screenshot.
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();
rm.getRenderer().readFrameBuffer(fb, byteBuf);
Screenshots.convertScreenShot2(intBuf, img);
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){
// All operations on strategy should be synchronized (?)
@ -235,44 +308,65 @@ public class AwtPanel extends Canvas implements SceneProcessor {
if (this.rm == null){
// First time called in OGL thread
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) {
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
intBuf = byteBuf.asIntBuffer();
invalidatePendingFrames();
if (fb != null) {
for (FrameBuffer fb : fbs) {
fb.dispose();
fb = null;
}
fbs.clear();
fb = new FrameBuffer(width, height, 1);
for (int i = 0; i < NUM_FRAMES; i++) {
FrameBuffer fb = new FrameBuffer(width, height, 1);
fb.setDepthBuffer(Format.Depth);
fb.setColorBuffer(Format.RGB8);
fb.setSrgb(srgb);
if (attachAsMain){
rm.getRenderer().setMainFrameBufferOverride(fb);
fb.setColorBuffer(Format.RGBA8);
fbs.add(fb);
}
// if (attachAsMain){
// rm.getRenderer().setMainFrameBufferOverride(fb);
// }
synchronized (lock){
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
}
// synchronized (lock){
// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
// }
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -img.getHeight());
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
for (ViewPort vp : viewPorts){
if (!attachAsMain){
vp.setOutputFrameBuffer(fb);
}
// if (!attachAsMain){
// vp.setOutputFrameBuffer(fb);
// }
vp.getCamera().resize(width, height, true);
// 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() {
return fb != null;
return rm != null;
}
public void preFrame(float tpf) {
@ -299,7 +394,20 @@ public class AwtPanel extends Canvas implements SceneProcessor {
repaintRequest.set(true);
}
void onFrameEnd() {
@Override
public Component getComponent() {
return this;
}
@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 {
@ -309,26 +417,24 @@ public class AwtPanel extends Canvas implements SceneProcessor {
switch (paintMode) {
case Accelerated:
drawFrameInThread();
break;
case Repaint:
repaintInThread();
break;
case OnRequest:
if (repaintRequest.getAndSet(false)) {
repaintInThread();
}
updateAccelerated();
break;
// case Repaint:
// repaintInThread();
// break;
// case OnRequest:
// if (repaintRequest.getAndSet(false)) {
// repaintInThread();
// }
// break;
}
}
}
public void postFrame(FrameBuffer out) {
if (!attachAsMain && out != fb){
throw new IllegalStateException("Why did you change the output framebuffer?");
}
// onFrameEnd();
// if (!attachAsMain && out != fb){
// throw new IllegalStateException("Why did you change the output framebuffer?");
// }
}
public void reshape(ViewPort vp, int w, int h) {

@ -46,8 +46,8 @@ public class AwtPanelsContext implements JmeContext {
protected JmeContext actualContext;
protected AppSettings settings = new AppSettings(true);
protected SystemListener listener;
protected ArrayList<AwtPanel> panels = new ArrayList<AwtPanel>();
protected AwtPanel inputSource;
protected ArrayList<JmePanel> panels = new ArrayList<JmePanel>();
protected JmePanel inputSource;
protected AwtMouseInput mouseInput = new AwtMouseInput();
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))
throw new IllegalArgumentException();
inputSource = panel;
mouseInput.setInputSource(panel);
keyInput.setInputSource(panel);
mouseInput.setInputSource(panel.getComponent());
keyInput.setInputSource(panel.getComponent());
}
public Type getType() {
@ -148,14 +148,14 @@ public class AwtPanelsContext implements JmeContext {
public AwtPanelsContext(){
}
public AwtPanel createPanel(PaintMode paintMode){
AwtPanel panel = new AwtPanel(paintMode);
public JmePanel createPanel(PaintMode paintMode){
JmePanel panel = new SwingPanel(paintMode, true);
panels.add(panel);
return panel;
}
public AwtPanel createPanel(PaintMode paintMode, boolean srgb){
AwtPanel panel = new AwtPanel(paintMode, srgb);
public JmePanel createPanel(PaintMode paintMode, boolean srgb){
JmePanel panel = new SwingPanel(paintMode, srgb);
panels.add(panel);
return panel;
}
@ -168,7 +168,7 @@ public class AwtPanelsContext implements JmeContext {
// Check if throttle required
boolean needThrottle = true;
for (AwtPanel panel : panels){
for (JmePanel panel : panels){
if (panel.isActiveDrawing()){
needThrottle = false;
break;
@ -191,9 +191,13 @@ public class AwtPanelsContext implements JmeContext {
}
}
for (JmePanel panel : panels){
panel.onFrameBegin();
}
listener.update();
for (AwtPanel panel : panels){
for (JmePanel panel : panels){
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.DefaultJoystickAxis;
@ -12,6 +12,7 @@ import com.jme3.input.JoystickCompatibilityMappings;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.system.NativeLibraryLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -32,25 +33,13 @@ public class JInputJoyInput implements JoyInput {
private JInputJoystick[] joysticks;
private RawInputListener listener;
private 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);
}
}
private final Map<Controller, JInputJoystick> joystickIndex = new HashMap<Controller, JInputJoystick>();
@Override
public Joystick[] loadJoysticks(InputManager inputManager){
ControllerEnvironment ce =
ControllerEnvironment.getDefaultEnvironment();
Controller[] cs = ce.getControllers();
List<Joystick> list = new ArrayList<Joystick>();
for( Controller c : ce.getControllers() ) {
if (c.getType() == Controller.Type.KEYBOARD
@ -82,10 +71,18 @@ public class JInputJoyInput implements JoyInput {
return joysticks;
}
@Override
public void initialize() {
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() {
ControllerEnvironment ce =
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() {
inited = false;
}
@Override
public boolean isInitialized() {
return inited;
}
@Override
public void setInputListener(RawInputListener listener) {
this.listener = listener;
}
@Override
public long getInputTimeNanos() {
return 0;
}
protected class JInputJoystick extends AbstractJoystick {
private static class JInputJoystick extends AbstractJoystick {
private JoystickAxis nullAxis;
private final JoystickAxis nullAxis;
private Controller controller;
private JoystickAxis xAxis;
private JoystickAxis yAxis;
@ -216,8 +230,8 @@ public class JInputJoyInput implements JoyInput {
String name = comp.getName();
String original = id.getName();
String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
if( name != original ) {
logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId);
if (!logicalId.equals(original)) {
logger.log(Level.FINE, "Remapped:{0} to:{1}", new Object[]{original, logicalId});
}
JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(),
@ -238,8 +252,8 @@ public class JInputJoyInput implements JoyInput {
String name = comp.getName();
String original = id.getName();
String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
if( name != original ) {
logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId);
if (!logicalId.equals(original)) {
logger.log(Level.FINE, "Remapped:{0} to:{1}", new Object[]{original, logicalId});
}
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 {
compile project(':jme3-core')
compile project(':jme3-desktop')
compile project(':jme3-jinput')
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.MouseInput;
import com.jme3.input.TouchInput;
import com.jme3.input.lwjgl.JInputJoyInput;
import com.jme3.input.lwjgl.LwjglKeyInput;
import com.jme3.input.lwjgl.LwjglMouseInput;
import com.jme3.input.jinput.JInputJoyInput;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeSystem;
import java.util.concurrent.atomic.AtomicBoolean;

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

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

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

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

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

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

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

@ -31,6 +31,8 @@
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.math.Matrix4f;
public class FbxAnimUtil {
/**
* 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_Z = "d|Z";
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;
float[] matData = null;
double[] matData = null;
for (FbxElement e : child.children) {
if (e.id.equals("Node")) {
node = FbxId.create(e.properties.get(0));
} 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
throw new UnsupportedOperationException("Bind pose matrix "
+ "must have 16 doubles, but it has "
+ matDataDoubles.length + ". Data is corrupt");
}
matData = new float[16];
for (int i = 0; i < matDataDoubles.length; i++) {
matData[i] = (float) matDataDoubles[i];
+ matData.length + ". Data is corrupt");
}
}
}
if (node != null && matData != null) {
Matrix4f matrix = new Matrix4f(matData);
bindPose.put(node, matrix);
bindPose.put(node, FbxAnimUtil.toMatrix4(matData));
}
}
}

@ -32,6 +32,7 @@
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.math.Matrix4f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.logging.Level;
@ -45,6 +46,10 @@ public class FbxCluster extends FbxObject {
private double[] weights;
private FbxLimbNode limb;
private Matrix4f transformMatrix;
private Matrix4f transformLinkMatrix;
private Matrix4f transformAssociateModelMatrix;
public FbxCluster(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@ -57,6 +62,15 @@ public class FbxCluster extends FbxObject {
indexes = (int[]) e.properties.get(0);
} else if (e.id.equals("Weights")) {
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;
}
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 {
unsupportedConnectObject(object);
}

@ -31,64 +31,22 @@
*/
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.scene.plugins.fbx.node.FbxNode;
import java.util.ArrayList;
import java.util.List;
public class FbxLimbNode extends FbxNode {
protected FbxNode skeletonHolder;
protected Bone bone;
protected FbxNode skeletonRoot;
public FbxLimbNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
private static void createBones(FbxNode skeletonHolderNode, FbxLimbNode limb, List<Bone> bones) {
limb.skeletonHolder = skeletonHolderNode;
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 FbxNode getSkeletonRoot() {
return skeletonRoot;
}
public static Skeleton createSkeleton(FbxNode skeletonHolderNode) {
if (skeletonHolderNode instanceof FbxLimbNode) {
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;
public void setSkeletonRoot(FbxNode skeletonRoot) {
this.skeletonRoot = skeletonRoot;
}
}

@ -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,21 +31,91 @@
*/
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.math.Transform;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.ArrayList;
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>();
public FbxSkinDeformer(AssetManager assetManager, String 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
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;
}

@ -34,6 +34,7 @@ package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
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) {
/*
Transform t = new Transform();
t.setTranslation(translation);
t.setRotation(rotation);
@ -111,6 +113,24 @@ public final class FbxToJmeTrack {
if (scale != null) {
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) {
@ -154,7 +174,7 @@ public final class FbxToJmeTrack {
if (i > 0) {
if (rotations[i - 1].dot(rotations[i]) < 0) {
System.out.println("rotation will go the long way, oh noes");
rotations[i - 1].negate();
rotations[i].negate();
}
}
} else {

@ -31,6 +31,9 @@
*/
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.PrintStream;
import java.lang.reflect.Array;
@ -88,6 +91,28 @@ public final class FbxDump {
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.
*

@ -307,7 +307,11 @@ public class FbxMaterial extends FbxObject<Material> {
if (useAlphaBlend) {
// No idea if this is a transparent or translucent model, gotta guess..
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);
}

@ -111,43 +111,44 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
}
public void applyCluster(FbxCluster cluster) {
if (boneIndices == null) {
boneIndices = new ArrayList[positions.length];
boneWeights = new ArrayList[positions.length];
}
FbxLimbNode limb = cluster.getLimb();
Bone bone = limb.getJmeBone();
Skeleton skeleton = limb.getSkeletonHolder().getJmeSkeleton();
int boneIndex = skeleton.getBoneIndex(bone);
int[] positionIndices = cluster.getVertexIndices();
double[] weights = cluster.getWeights();
for (int i = 0; i < positionIndices.length; i++) {
int positionIndex = positionIndices[i];
float boneWeight = (float)weights[i];
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex];
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex];
if (boneIndicesForVertex == null) {
boneIndicesForVertex = new ArrayList<Integer>();
boneWeightsForVertex = new ArrayList<Float>();
boneIndices[positionIndex] = boneIndicesForVertex;
boneWeights[positionIndex] = boneWeightsForVertex;
}
boneIndicesForVertex.add(boneIndex);
boneWeightsForVertex.add(boneWeight);
}
// if (boneIndices == null) {
// boneIndices = new ArrayList[positions.length];
// boneWeights = new ArrayList[positions.length];
// }
//
// FbxLimbNode limb = cluster.getLimb();
// Bone bone = limb.getJmeBone();
// Skeleton skeleton = limb.getSkeletonRoot().getJmeSkeleton();
// int boneIndex = skeleton.getBoneIndex(bone);
//
// int[] positionIndices = cluster.getVertexIndices();
// double[] weights = cluster.getWeights();
//
// for (int i = 0; i < positionIndices.length; i++) {
// int positionIndex = positionIndices[i];
// float boneWeight = (float)weights[i];
//
// ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex];
// ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex];
//
// if (boneIndicesForVertex == null) {
// boneIndicesForVertex = new ArrayList<Integer>();
// boneWeightsForVertex = new ArrayList<Float>();
// boneIndices[positionIndex] = boneIndicesForVertex;
// boneWeights[positionIndex] = boneWeightsForVertex;
// }
//
// boneIndicesForVertex.add(boneIndex);
// boneWeightsForVertex.add(boneWeight);
// }
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxSkinDeformer) {
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;
}
skinDeformer = (FbxSkinDeformer) object;
@ -209,7 +210,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
protected IntMap<Mesh> toJmeObject() {
// Load clusters from SkinDeformer
if (skinDeformer != null) {
for (FbxCluster cluster : skinDeformer.getJmeObject()) {
for (FbxCluster cluster : skinDeformer.getClusters()) {
applyCluster(cluster);
}
}
@ -237,7 +238,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
if (jmeMeshes.size() == 0) {
// 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
@ -245,7 +246,8 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
// It makes sense only if the mesh uses a single material!
if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) {
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;

@ -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.FbxCluster;
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.file.FbxElement;
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).
*/
protected Skeleton skeleton;
protected FbxSkeleton skeleton;
protected final Transform jmeWorldNodeTransform = 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();
userDataValue = new Vector3f(x, y, z);
} else {
logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType);
logger.log(Level.WARNING, "Unsupported user data type: {0}. "
+ "Ignoring.", userDataType);
continue;
}
@ -329,6 +331,9 @@ public class FbxNode extends FbxObject<Spatial> {
// Material index does not exist. Create default material.
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
jmeMat.setReceivesShadows(true);
logger.log(Level.WARNING, "Material index {0} is undefined in: {1}. "
+ "Will use default material.",
new Object[]{materialIndex, this});
} else {
FbxMaterial fbxMat = materials.get(materialIndex);
jmeMat = fbxMat.getJmeObject();
@ -376,11 +381,11 @@ public class FbxNode extends FbxObject<Spatial> {
FbxNode preferredParent = null;
if (deformer != null) {
for (FbxCluster cluster : deformer.getJmeObject()) {
for (FbxCluster cluster : deformer.getClusters()) {
FbxLimbNode limb = cluster.getLimb();
if (preferredParent == null) {
preferredParent = limb.getSkeletonHolder();
} else if (preferredParent != limb.getSkeletonHolder()) {
preferredParent = limb.getSkeletonRoot();
} else if (preferredParent != limb.getSkeletonRoot()) {
logger.log(Level.WARNING, "A mesh is being deformed by multiple 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) {
// 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");
} else {
// 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) ||
!FastMath.approximateEquals(localScale.x, localScale.z)) {
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) {
throw new UnsupportedOperationException();
}
fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
System.out.println("created skeleton: " + fbxNode.skeleton);
// fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
// System.out.println("created skeleton: " + fbxNode.skeleton);
}
}
@ -518,19 +524,19 @@ public class FbxNode extends FbxObject<Spatial> {
}
}
if (fbxNode.skeleton != null) {
jmeSpatial.addControl(new AnimControl(fbxNode.skeleton));
jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton));
SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton);
Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.getAdditionalRenderState().setWireframe(true);
mat.getAdditionalRenderState().setDepthTest(false);
mat.setColor("Color", ColorRGBA.Green);
sd.setMaterial(mat);
((Node)jmeSpatial).attachChild(sd);
}
// if (fbxNode.skeleton != null) {
// jmeSpatial.addControl(new AnimControl(fbxNode.skeleton));
// jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton));
//
// SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton);
// Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// mat.getAdditionalRenderState().setWireframe(true);
// mat.getAdditionalRenderState().setDepthTest(false);
// mat.setColor("Color", ColorRGBA.Green);
// sd.setMaterial(mat);
//
// ((Node)jmeSpatial).attachChild(sd);
// }
return jmeSpatial;
}
@ -543,10 +549,18 @@ public class FbxNode extends FbxObject<Spatial> {
// return limb;
// }
public Skeleton getJmeSkeleton() {
// public Skeleton getJmeSkeleton() {
// return skeleton;
// }
public FbxSkeleton getFbxSkeleton() {
return skeleton;
}
public void setFbxSkeleton(FbxSkeleton skeleton) {
this.skeleton = skeleton;
}
public List<FbxNode> getChildren() {
return children;
}

@ -37,6 +37,7 @@ import com.jme3.scene.VertexBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.scene.mesh.IndexIntBuffer;
import com.jme3.scene.mesh.IndexShortBuffer;
import com.jme3.scene.plugins.triangulator.EarClippingTriangulator;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
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.
*/
public static void triangulate(IrMesh mesh) {
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
EarClippingTriangulator triangulator = new EarClippingTriangulator();
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);
newPolygons.add(tris[0]);
newPolygons.add(tris[1]);
} else if (inputPoly.vertices.length == 3) {
newPolygons.add(inputPoly);
} else {
// N-gon. We have to ignore it..
logger.log(Level.WARNING, "N-gon encountered, ignoring. "
+ "The mesh may not appear correctly. "
+ "Triangulate your model prior to export.");
// N-gon
dumpPoly(inputPoly);
IrPolygon[] tris = triangulator.triangulate(inputPoly);
for (IrPolygon tri : tris) {
newPolygons.add(tri);
}
}
}
mesh.polygons = new IrPolygon[newPolygons.size()];
@ -373,12 +391,11 @@ public final class IrUtils {
boneIndices.put((byte)0);
boneWeights.put(0f);
}
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
} else {
boneIndices.putInt(0);
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-blender'
include 'jme3-jogl'
include 'jme3-jinput'
include 'jme3-lwjgl'
include 'jme3-lwjgl3'

@ -54,6 +54,7 @@ ext {
jmeFullVersion = "${jmeVersion}-UNKNOWN"
jmePomVersion = "unknown"
jmeNbmUcSuffix = "unknown"
jmeVersionTag = "unknown"
}
def getReleaseInfo(String tag) {
@ -135,6 +136,7 @@ task configureVersionInfo {
jmePomVersion = "${releaseInfo.mainVersion}${releaseInfo.releaseSuffix}"
jmeNbmRevision = "0"
jmeNbmUcSuffix = "stable/${releaseInfo.baseVersion}/plugins"
jmeVersionTag = releaseInfo.releaseName ?: "";
} else {
// SNAPSHOT
jmeFullVersion = jmeMainVersion
@ -157,6 +159,7 @@ task configureVersionInfo {
jmeFullVersion += "-${jmeRevision}"
jmePomVersion += "-SNAPSHOT"
jmeNbmRevision = jmeRevision
jmeVersionTag = "SNAPSHOT"
}
logger.warn("Full Version: ${jmeFullVersion}")

Loading…
Cancel
Save