iwgeric 10 years ago
commit da8bd08aa1
  1. 3
      jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
  2. 3
      jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
  3. 2
      jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
  4. 20
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  5. 19
      jme3-core/src/main/java/com/jme3/math/FastMath.java
  6. 20
      jme3-core/src/main/java/com/jme3/math/Transform.java
  7. 14
      jme3-core/src/main/java/com/jme3/renderer/Caps.java
  8. 3
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  9. 10
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
  10. 26
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
  11. 14
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
  12. 4
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
  13. 3
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
  14. 38
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  15. 166
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  16. 122
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java
  17. 41
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java
  18. 7
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
  19. 1
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
  20. 24
      jme3-core/src/main/java/com/jme3/texture/Image.java
  21. 20
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
  22. 13
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  23. 12
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  24. 23
      jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java
  25. 4
      jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java
  26. 6
      jme3-ios/src/main/java/com/jme3/asset/IOS.cfg
  27. 1054
      jme3-ios/src/main/java/com/jme3/audio/android/AL.java
  28. 67
      jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java
  29. 53
      jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java
  30. 26
      jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java
  31. 32
      jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java
  32. 20
      jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java
  33. 2573
      jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java
  34. 8
      jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java
  35. 576
      jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java
  36. 5
      jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
  37. 15
      jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java
  38. 15
      jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java
  39. 10
      jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg
  40. 15
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
  41. 68
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java
  42. 98
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java
  43. 96
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java
  44. 35
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  45. 78
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java
  46. 8
      jme3-networking/src/main/java/com/jme3/network/Client.java
  47. 8
      jme3-networking/src/main/java/com/jme3/network/Server.java
  48. 29
      jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
  49. 58
      jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java
  50. 188
      jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java
  51. 51
      jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java
  52. 70
      jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java
  53. 111
      jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java
  54. 72
      jme3-networking/src/main/java/com/jme3/network/service/ClientService.java
  55. 100
      jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java
  56. 74
      jme3-networking/src/main/java/com/jme3/network/service/HostedService.java
  57. 140
      jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java
  58. 67
      jme3-networking/src/main/java/com/jme3/network/service/Service.java
  59. 160
      jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java
  60. 123
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java
  61. 260
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java
  62. 52
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java
  63. 227
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java
  64. 98
      jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java
  65. 89
      jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java
  66. 69
      jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java
  67. 72
      jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java
  68. 314
      jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java
  69. 72
      jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java
  70. 103
      jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java
  71. 9
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
  72. 413
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java
  73. 84
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
  74. 144
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java
  75. 147
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java
  76. 82
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java
  77. 111
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java
  78. 44
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java
  79. 103
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java
  80. 98
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java
  81. 94
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java
  82. 66
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java
  83. 202
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java
  84. 76
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java
  85. 124
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java
  86. 8
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java
  87. 130
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java
  88. 22
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java
  89. 190
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java
  90. 363
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java
  91. 234
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java
  92. 146
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java
  93. 107
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java
  94. 243
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java
  95. 316
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java
  96. 69
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java
  97. 55
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java
  98. 145
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java
  99. 617
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java
  100. 41
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -43,6 +43,9 @@ import java.nio.ShortBuffer;
public class AndroidGL implements GL, GLExt {
public void resetStats() {
}
private static int getLimitBytes(ByteBuffer buffer) {
checkLimit(buffer);
return buffer.limit();

@ -54,6 +54,7 @@ import com.jme3.input.dummy.DummyMouseInput;
import com.jme3.renderer.android.AndroidGL;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.system.*;
import java.util.concurrent.atomic.AtomicBoolean;
@ -196,7 +197,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
Object gl = new AndroidGL();
// gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl);
// gl = new GLDebugES((GL)gl, (GLExt)gl);
renderer = new GLRenderer((GL)gl, (GLExt)gl);
renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl);
renderer.initialize();
JmeSystem.setSoftTextDialogInput(this);

@ -195,7 +195,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
/**
* When set to true, the physics coordinates will be applied to the local
* translation of the Spatial instead of the world traslation.
* translation of the Spatial instead of the world translation.
* @param applyPhysicsLocal
*/
public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {

@ -313,6 +313,26 @@ public final class Bone implements Savable {
return modelBindInverseScale;
}
public Transform getModelBindInverseTransform() {
Transform t = new Transform();
t.setTranslation(modelBindInversePos);
t.setRotation(modelBindInverseRot);
if (modelBindInverseScale != null) {
t.setScale(modelBindInverseScale);
}
return t;
}
public Transform getBindInverseTransform() {
Transform t = new Transform();
t.setTranslation(bindPos);
t.setRotation(bindRot);
if (bindScale != null) {
t.setScale(bindScale);
}
return t.invert();
}
/**
* @deprecated use {@link #getBindPosition()}
*/

@ -902,6 +902,25 @@ final public class FastMath {
return clamp(input, 0f, 1f);
}
/**
* Determine if two floats are approximately equal.
* This takes into account the magnitude of the floats, since
* large numbers will have larger differences be close to each other.
*
* Should return true for a=100000, b=100001, but false for a=10000, b=10001.
*
* @param a The first float to compare
* @param b The second float to compare
* @return True if a and b are approximately equal, false otherwise.
*/
public static boolean approximateEquals(float a, float b) {
if (a == b) {
return true;
} else {
return (abs(a - b) / Math.max(abs(a), abs(b))) <= 0.00001f;
}
}
/**
* Converts a single precision (32 bit) floating point value
* into half precision (16 bit).

@ -259,6 +259,26 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
return store;
}
public Matrix4f toTransformMatrix() {
Matrix4f trans = new Matrix4f();
trans.setTranslation(translation);
trans.setRotationQuaternion(rot);
trans.setScale(scale);
return trans;
}
public void fromTransformMatrix(Matrix4f mat) {
translation.set(mat.toTranslationVector());
rot.set(mat.toRotationQuat());
scale.set(mat.toScaleVector());
}
public Transform invert() {
Transform t = new Transform();
t.fromTransformMatrix(toTransformMatrix().invertLocal());
return t;
}
/**
* Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1.
*/

@ -337,7 +337,19 @@ public enum Caps {
* <p>
* Improves the quality of environment mapping.
*/
SeamlessCubemap;
SeamlessCubemap,
/**
* Running with OpenGL 3.2+ core profile.
*
* Compatibility features will not be available.
*/
CoreProfile,
/**
* GPU can provide and accept binary shaders.
*/
BinaryShader;
/**
* Returns true if given the renderer capabilities, the texture

@ -32,7 +32,6 @@
package com.jme3.renderer.opengl;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
@ -178,6 +177,8 @@ public interface GL {
public static final int GL_VERTEX_SHADER = 0x8B31;
public static final int GL_ZERO = 0x0;
public void resetStats();
public void glActiveTexture(int texture);
public void glAttachShader(int program, int shader);
public void glBindBuffer(int target, int buffer);

@ -35,14 +35,18 @@ import java.nio.IntBuffer;
/**
* GL functions only available on vanilla desktop OpenGL 3.0.
*
*
* @author Kirill Vainer
*/
public interface GL3 extends GL2 {
public static final int GL_DEPTH_STENCIL_ATTACHMENT = 0x821A;
public static final int GL_GEOMETRY_SHADER=0x8DD9;
public static final int GL_GEOMETRY_SHADER = 0x8DD9;
public static final int GL_NUM_EXTENSIONS = 0x821D;
public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+
public void glBindVertexArray(int param1); /// GL3+
public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+
public void glGenVertexArrays(IntBuffer param1); /// GL3+
public String glGetString(int param1, int param2); /// GL3+
}

@ -3,15 +3,17 @@ package com.jme3.renderer.opengl;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
private final GL2 gl2;
private final GL3 gl3;
private final GL4 gl4;
public GLDebugDesktop(GL gl, GLFbo glfbo) {
super(gl, glfbo);
public GLDebugDesktop(GL gl, GLExt glext, GLFbo glfbo) {
super(gl, glext, glfbo);
this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
this.gl4 = gl instanceof GL4 ? (GL4) gl : null;
}
public void glAlphaFunc(int func, float ref) {
@ -73,5 +75,23 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
gl3.glGenVertexArrays(param1);
checkError();
}
@Override
public String glGetString(int param1, int param2) {
String result = gl3.glGetString(param1, param2);
checkError();
return result;
}
@Override
public void glDeleteVertexArrays(IntBuffer arrays) {
gl3.glDeleteVertexArrays(arrays);
checkError();
}
@Override
public void glPatchParameter(int count) {
gl4.glPatchParameter(count);
checkError();
}
}

@ -10,14 +10,16 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
private final GLFbo glfbo;
private final GLExt glext;
public GLDebugES(GL gl, GLFbo glfbo) {
public GLDebugES(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
// this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
// this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
this.glext = glext;
this.glfbo = glfbo;
this.glext = glfbo instanceof GLExt ? (GLExt) glfbo : null;
}
public void resetStats() {
gl.resetStats();
}
public void glActiveTexture(int texture) {
gl.glActiveTexture(texture);
checkError();
@ -478,7 +480,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
}
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
checkError();
}
@ -525,7 +527,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
}
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
glext.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
glfbo.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
checkError();
}

@ -41,7 +41,7 @@ import java.nio.IntBuffer;
*
* @author Kirill Vainer
*/
public interface GLExt extends GLFbo {
public interface GLExt {
public static final int GL_ALREADY_SIGNALED = 0x911A;
public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274;
@ -100,7 +100,6 @@ public interface GLExt extends GLFbo {
public static final int GL_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E;
public static final int GL_WAIT_FAILED = 0x911D;
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public void glBufferData(int target, IntBuffer data, int usage);
public void glBufferSubData(int target, long offset, IntBuffer data);
public int glClientWaitSync(Object sync, int flags, long timeout);
@ -110,7 +109,6 @@ public interface GLExt extends GLFbo {
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount);
public Object glFenceSync(int condition, int flags);
public void glGetMultisample(int pname, int index, FloatBuffer val);
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations);
public void glVertexAttribDivisorARB(int index, int divisor);
}

@ -83,6 +83,7 @@ public interface GLFbo {
public void glBindFramebufferEXT(int param1, int param2);
public void glBindRenderbufferEXT(int param1, int param2);
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public int glCheckFramebufferStatusEXT(int param1);
public void glDeleteFramebuffersEXT(IntBuffer param1);
public void glDeleteRenderbuffersEXT(IntBuffer param1);
@ -92,5 +93,5 @@ public interface GLFbo {
public void glGenRenderbuffersEXT(IntBuffer param1);
public void glGenerateMipmapEXT(int param1);
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4);
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
}

@ -89,9 +89,11 @@ public final class GLImageFormats {
GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length];
if (caps.contains(Caps.OpenGL20)) {
format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
format(formatToGL, Format.RGB8, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGB565, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5);
@ -108,8 +110,10 @@ public final class GLImageFormats {
formatSrgb(formatToGL, Format.RGB565, GLExt.GL_SRGB8_EXT, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5);
formatSrgb(formatToGL, Format.RGB5A1, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
formatSrgb(formatToGL, Format.RGBA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
formatSrgb(formatToGL, Format.BGR8, GLExt.GL_SRGB8_EXT, GL2.GL_BGR, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.ABGR8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL2.GL_UNSIGNED_INT_8_8_8_8);
formatSrgb(formatToGL, Format.ARGB8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8);
@ -124,16 +128,20 @@ public final class GLImageFormats {
}
} else if (caps.contains(Caps.Rgba8)) {
// A more limited form of 32-bit RGBA. Only GL_RGBA8 is available.
format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
format(formatToGL, Format.RGB8, GLExt.GL_RGBA8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
} else {
// Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above..
format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
format(formatToGL, Format.RGB8, GL.GL_RGB565, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GL.GL_RGBA4, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
}
@ -145,9 +153,11 @@ public final class GLImageFormats {
format(formatToGL, Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
if (caps.contains(Caps.FloatTexture)) {
format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB);
}
format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT);
format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, GLExt.GL_HALF_FLOAT_ARB);

@ -56,6 +56,7 @@ import com.jme3.util.BufferUtils;
import com.jme3.util.ListMap;
import com.jme3.util.NativeObjectManager;
import java.nio.*;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
@ -112,13 +113,13 @@ public class GLRenderer implements Renderer {
private final GLFbo glfbo;
private final TextureUtil texUtil;
public GLRenderer(GL gl, GLFbo glfbo) {
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
this.gl2 = gl instanceof GL2 ? (GL2)gl : null;
this.gl3 = gl instanceof GL3 ? (GL3)gl : null;
this.gl4 = gl instanceof GL4 ? (GL4)gl : null;
this.glfbo = glfbo;
this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null;
this.glext = glext;
this.texUtil = new TextureUtil(gl, gl2, glext, context);
}
@ -137,10 +138,19 @@ public class GLRenderer implements Renderer {
return limits;
}
private static HashSet<String> loadExtensions(String extensions) {
private HashSet<String> loadExtensions() {
HashSet<String> extensionSet = new HashSet<String>(64);
for (String extension : extensions.split(" ")) {
extensionSet.add(extension);
if (gl3 != null) {
// If OpenGL3+ is available, use the non-deprecated way
// of getting supported extensions.
gl3.glGetInteger(GL3.GL_NUM_EXTENSIONS, intBuf16);
int extensionCount = intBuf16.get(0);
for (int i = 0; i < extensionCount; i++) {
String extension = gl3.glGetString(GL.GL_EXTENSIONS, i);
extensionSet.add(extension);
}
} else {
extensionSet.addAll(Arrays.asList(gl.glGetString(GL.GL_EXTENSIONS).split(" ")));
}
return extensionSet;
}
@ -185,10 +195,12 @@ public class GLRenderer implements Renderer {
caps.add(Caps.OpenGL31);
if (oglVer >= 320) {
caps.add(Caps.OpenGL32);
}if(oglVer>=330){
}
if (oglVer >= 330) {
caps.add(Caps.OpenGL33);
caps.add(Caps.GeometryShader);
}if(oglVer>=400){
}
if (oglVer >= 400) {
caps.add(Caps.OpenGL40);
caps.add(Caps.TesselationShader);
}
@ -243,7 +255,7 @@ public class GLRenderer implements Renderer {
}
private void loadCapabilitiesCommon() {
extensions = loadExtensions(gl.glGetString(GL.GL_EXTENSIONS));
extensions = loadExtensions();
limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
if (limits.get(Limits.VertexTextureUnits) > 0) {
@ -251,7 +263,7 @@ public class GLRenderer implements Renderer {
}
limits.put(Limits.FragmentTextureUnits, getInteger(GL.GL_MAX_TEXTURE_IMAGE_UNITS));
// gl.glGetInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16);
// vertexUniforms = intBuf16.get(0);
// logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms);
@ -279,7 +291,7 @@ public class GLRenderer implements Renderer {
// == texture format extensions ==
boolean hasFloatTexture = false;
boolean hasFloatTexture;
hasFloatTexture = hasExtension("GL_OES_texture_half_float") &&
hasExtension("GL_OES_texture_float");
@ -375,11 +387,11 @@ public class GLRenderer implements Renderer {
caps.add(Caps.TextureFilterAnisotropic);
}
if (hasExtension("GL_EXT_framebuffer_object")) {
if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) {
caps.add(Caps.FrameBuffer);
limits.put(Limits.RenderBufferSize, getInteger(GLExt.GL_MAX_RENDERBUFFER_SIZE_EXT));
limits.put(Limits.FrameBufferAttachments, getInteger(GLExt.GL_MAX_COLOR_ATTACHMENTS_EXT));
limits.put(Limits.RenderBufferSize, getInteger(GLFbo.GL_MAX_RENDERBUFFER_SIZE_EXT));
limits.put(Limits.FrameBufferAttachments, getInteger(GLFbo.GL_MAX_COLOR_ATTACHMENTS_EXT));
if (hasExtension("GL_EXT_framebuffer_blit")) {
caps.add(Caps.FrameBufferBlit);
@ -434,21 +446,30 @@ public class GLRenderer implements Renderer {
caps.add(Caps.SeamlessCubemap);
}
// if (hasExtension("GL_ARB_get_program_binary")) {
// int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS);
// }
if (caps.contains(Caps.OpenGL32) && !hasExtension("GL_ARB_compatibility")) {
caps.add(Caps.CoreProfile);
}
if (hasExtension("GL_ARB_get_program_binary")) {
int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS);
if (binaryFormats > 0) {
caps.add(Caps.BinaryShader);
}
}
// Print context information
logger.log(Level.INFO, "OpenGL Renderer Information\n" +
" * Vendor: {0}\n" +
" * Renderer: {1}\n" +
" * OpenGL Version: {2}\n" +
" * GLSL Version: {3}",
" * GLSL Version: {3}\n" +
" * Profile: {4}",
new Object[]{
gl.glGetString(GL.GL_VENDOR),
gl.glGetString(GL.GL_RENDERER),
gl.glGetString(GL.GL_VERSION),
gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)
gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION),
caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility"
});
// Print capabilities (if fine logging is enabled)
@ -491,6 +512,20 @@ public class GLRenderer implements Renderer {
// Initialize default state..
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
if (caps.contains(Caps.CoreProfile)) {
// Core Profile requires VAO to be bound.
gl3.glGenVertexArrays(intBuf16);
int vaoId = intBuf16.get(0);
gl3.glBindVertexArray(vaoId);
}
if (gl2 != null) {
gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
if (!caps.contains(Caps.CoreProfile)) {
gl2.glEnable(GL2.GL_POINT_SPRITE);
context.pointSprite = true;
}
}
}
public void invalidateState() {
@ -610,31 +645,6 @@ public class GLRenderer implements Renderer {
context.colorWriteEnabled = false;
}
if (gl2 != null) {
if (state.isPointSprite() && !context.pointSprite) {
// Only enable/disable sprite
if (context.boundTextures[0] != null) {
if (context.boundTextureUnit != 0) {
gl.glActiveTexture(GL.GL_TEXTURE0);
context.boundTextureUnit = 0;
}
gl2.glEnable(GL2.GL_POINT_SPRITE);
gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
}
context.pointSprite = true;
} else if (!state.isPointSprite() && context.pointSprite) {
if (context.boundTextures[0] != null) {
if (context.boundTextureUnit != 0) {
gl.glActiveTexture(GL.GL_TEXTURE0);
context.boundTextureUnit = 0;
}
gl2.glDisable(GL2.GL_POINT_SPRITE);
gl2.glDisable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
context.pointSprite = false;
}
}
}
if (state.isPolyOffset()) {
if (!context.polyOffsetEnabled) {
gl.glEnable(GL.GL_POLYGON_OFFSET_FILL);
@ -704,9 +714,6 @@ public class GLRenderer implements Renderer {
case AlphaAdditive:
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
break;
case Color:
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
break;
case Alpha:
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
break;
@ -719,6 +726,7 @@ public class GLRenderer implements Renderer {
case ModulateX2:
gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
break;
case Color:
case Screen:
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
break;
@ -862,6 +870,7 @@ public class GLRenderer implements Renderer {
public void postFrame() {
objManager.deleteUnused(this);
gl.resetStats();
}
/*********************************************************************\
@ -1290,24 +1299,24 @@ public class GLRenderer implements Renderer {
}
if (src == null) {
glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, 0);
glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, 0);
srcX0 = vpX;
srcY0 = vpY;
srcX1 = vpX + vpW;
srcY1 = vpY + vpH;
} else {
glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, src.getId());
glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, src.getId());
srcX1 = src.getWidth();
srcY1 = src.getHeight();
}
if (dst == null) {
glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, 0);
glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, 0);
dstX0 = vpX;
dstY0 = vpY;
dstX1 = vpX + vpW;
dstY1 = vpY + vpH;
} else {
glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, dst.getId());
glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, dst.getId());
dstX1 = dst.getWidth();
dstY1 = dst.getHeight();
}
@ -1315,12 +1324,12 @@ public class GLRenderer implements Renderer {
if (copyDepth) {
mask |= GL.GL_DEPTH_BUFFER_BIT;
}
glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1,
glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1,
dstX0, dstY0, dstX1, dstY1, mask,
GL.GL_NEAREST);
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, prevFBO);
glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, prevFBO);
} else {
throw new RendererException("Framebuffer blitting not supported by the video hardware");
}
@ -1366,7 +1375,7 @@ public class GLRenderer implements Renderer {
}
if (context.boundRB != id) {
glfbo.glBindRenderbufferEXT(GLExt.GL_RENDERBUFFER_EXT, id);
glfbo.glBindRenderbufferEXT(GLFbo.GL_RENDERBUFFER_EXT, id);
context.boundRB = id;
}
@ -1384,13 +1393,13 @@ public class GLRenderer implements Renderer {
if (maxSamples < samples) {
samples = maxSamples;
}
glext.glRenderbufferStorageMultisampleEXT(GLExt.GL_RENDERBUFFER_EXT,
glfbo.glRenderbufferStorageMultisampleEXT(GLFbo.GL_RENDERBUFFER_EXT,
samples,
glFmt.internalFormat,
fb.getWidth(),
fb.getHeight());
} else {
glfbo.glRenderbufferStorageEXT(GLExt.GL_RENDERBUFFER_EXT,
glfbo.glRenderbufferStorageEXT(GLFbo.GL_RENDERBUFFER_EXT,
glFmt.internalFormat,
fb.getWidth(),
fb.getHeight());
@ -1400,7 +1409,7 @@ public class GLRenderer implements Renderer {
private int convertAttachmentSlot(int attachmentSlot) {
// can also add support for stencil here
if (attachmentSlot == FrameBuffer.SLOT_DEPTH) {
return GLExt.GL_DEPTH_ATTACHMENT_EXT;
return GLFbo.GL_DEPTH_ATTACHMENT_EXT;
} else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) {
// NOTE: Using depth stencil format requires GL3, this is already
// checked via render caps.
@ -1409,7 +1418,7 @@ public class GLRenderer implements Renderer {
throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot);
}
return GLExt.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;
return GLFbo.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;
}
public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) {
@ -1427,7 +1436,7 @@ public class GLRenderer implements Renderer {
setupTextureParams(tex);
}
glfbo.glFramebufferTexture2DEXT(GLExt.GL_FRAMEBUFFER_EXT,
glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
image.getId(),
@ -1445,9 +1454,9 @@ public class GLRenderer implements Renderer {
updateRenderTexture(fb, rb);
}
if (needAttach) {
glfbo.glFramebufferRenderbufferEXT(GLExt.GL_FRAMEBUFFER_EXT,
glfbo.glFramebufferRenderbufferEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
GLExt.GL_RENDERBUFFER_EXT,
GLFbo.GL_RENDERBUFFER_EXT,
rb.getId());
}
}
@ -1465,7 +1474,7 @@ public class GLRenderer implements Renderer {
}
if (context.boundFBO != id) {
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, id);
glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, id);
// binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0
context.boundDrawBuf = 0;
context.boundFBO = id;
@ -1545,7 +1554,7 @@ public class GLRenderer implements Renderer {
if (fb == null) {
// unbind any fbos
if (context.boundFBO != 0) {
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0);
glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0);
statistics.onFrameBufferUse(null, true);
context.boundFBO = 0;
@ -1577,7 +1586,7 @@ public class GLRenderer implements Renderer {
setViewPort(0, 0, fb.getWidth(), fb.getHeight());
if (context.boundFBO != fb.getId()) {
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, fb.getId());
glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId());
statistics.onFrameBufferUse(fb, true);
context.boundFBO = fb.getId();
@ -1617,7 +1626,7 @@ public class GLRenderer implements Renderer {
if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) {
intBuf16.clear();
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
intBuf16.put(GLExt.GL_COLOR_ATTACHMENT0_EXT + i);
intBuf16.put(GLFbo.GL_COLOR_ATTACHMENT0_EXT + i);
}
intBuf16.flip();
@ -1629,7 +1638,7 @@ public class GLRenderer implements Renderer {
// select this draw buffer
if (gl2 != null) {
if (context.boundDrawBuf != rb.getSlot()) {
gl2.glDrawBuffer(GLExt.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
gl2.glDrawBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
context.boundDrawBuf = rb.getSlot();
}
}
@ -1658,7 +1667,7 @@ public class GLRenderer implements Renderer {
setFrameBuffer(fb);
if (gl2 != null) {
if (context.boundReadBuf != rb.getSlot()) {
gl2.glReadBuffer(GLExt.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
gl2.glReadBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
context.boundReadBuf = rb.getSlot();
}
}
@ -1682,7 +1691,7 @@ public class GLRenderer implements Renderer {
public void deleteFrameBuffer(FrameBuffer fb) {
if (fb.getId() != -1) {
if (context.boundFBO == fb.getId()) {
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0);
glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0);
context.boundFBO = 0;
}
@ -2620,32 +2629,13 @@ public class GLRenderer implements Renderer {
return;
}
if (context.pointSprite && mesh.getMode() != Mode.Points) {
// XXX: Hack, disable point sprite mode if mesh not in point mode
if (context.boundTextures[0] != null) {
if (context.boundTextureUnit != 0) {
gl.glActiveTexture(GL.GL_TEXTURE0);
context.boundTextureUnit = 0;
}
if (gl2 != null) {
gl2.glDisable(GL2.GL_POINT_SPRITE);
gl2.glDisable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
}
context.pointSprite = false;
}
}
if (gl2 != null) {
if (context.pointSize != mesh.getPointSize()) {
gl2.glPointSize(mesh.getPointSize());
context.pointSize = mesh.getPointSize();
}
}
if (context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth();
}
if(gl4!=null && mesh.getMode().equals(Mode.Patch)){
if (gl4 != null && mesh.getMode().equals(Mode.Patch)) {
gl4.glPatchParameter(mesh.getPatchVertexCount());
}
statistics.onMeshDrawn(mesh, lod, count);

@ -0,0 +1,122 @@
/*
* 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.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
public class GLTiming implements InvocationHandler {
private final Object obj;
private final GLTimingState state;
public GLTiming(Object obj, GLTimingState state) {
this.obj = obj;
this.state = state;
}
public static Object createGLTiming(Object glInterface, GLTimingState state, Class<?> ... glInterfaceClasses) {
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
glInterfaceClasses,
new GLTiming(glInterface, state));
}
private static class CallTimingComparator implements Comparator<Map.Entry<String, Long>> {
@Override
public int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
return (int) (o2.getValue() - o1.getValue());
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("resetStats")) {
if (state.lastPrintOutTime + 1000000000 <= System.nanoTime() && state.sampleCount > 0) {
state.timeSpentInGL /= state.sampleCount;
System.out.println("--- TOTAL TIME SPENT IN GL CALLS: " + (state.timeSpentInGL/1000) + "us");
Map.Entry<String, Long>[] callTimes = new Map.Entry[state.callTiming.size()];
int i = 0;
for (Map.Entry<String, Long> callTime : state.callTiming.entrySet()) {
callTimes[i++] = callTime;
}
Arrays.sort(callTimes, new CallTimingComparator());
int limit = 10;
for (Map.Entry<String, Long> callTime : callTimes) {
long val = callTime.getValue() / state.sampleCount;
String name = callTime.getKey();
String pad = " ".substring(0, 30 - name.length());
System.out.println("\t" + callTime.getKey() + pad + (val/1000) + "us");
if (limit-- == 0) break;
}
for (Map.Entry<String, Long> callTime : callTimes) {
state.callTiming.put(callTime.getKey(), Long.valueOf(0));
}
state.sampleCount = 0;
state.timeSpentInGL = 0;
state.lastPrintOutTime = System.nanoTime();
} else {
state.sampleCount++;
}
return null;
} else {
Long currentTimeObj = state.callTiming.get(methodName);
long currentTime = 0;
if (currentTimeObj != null) currentTime = currentTimeObj;
long startTime = System.nanoTime();
Object result = method.invoke(obj, args);
long delta = System.nanoTime() - startTime;
currentTime += delta;
state.timeSpentInGL += delta;
state.callTiming.put(methodName, currentTime);
if (delta > 1000000 && !methodName.equals("glClear")) {
// More than 1ms
// Ignore glClear as it cannot be avoided.
System.out.println("GL call " + methodName + " took " + (delta/1000) + "us to execute!");
}
return result;
}
}
}

@ -0,0 +1,41 @@
/*
* 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.util.HashMap;
public class GLTimingState {
long timeSpentInGL = 0;
int sampleCount = 0;
long lastPrintOutTime = 0;
final HashMap<String, Long> callTiming = new HashMap<String, Long>();
}

@ -64,6 +64,7 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glScissor", 0, 1, 2, 3);
noEnumArgs("glClear", 0);
noEnumArgs("glGetInteger", 1);
noEnumArgs("glGetString", 1);
noEnumArgs("glBindTexture", 1);
noEnumArgs("glPixelStorei", 1);
@ -95,8 +96,6 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glFramebufferTexture2DEXT", 3, 4);
noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8);
noEnumArgs("glCreateProgram", -1);
noEnumArgs("glCreateShader", -1);
noEnumArgs("glShaderSource", 0);
@ -155,7 +154,7 @@ public final class GLTracer implements InvocationHandler {
* @return A tracer that implements the given interface
*/
public static Object createGlesTracer(Object glInterface, Class<?> glInterfaceClass) {
IntMap<String> constMap = generateConstantMap(GL.class, GLExt.class);
IntMap<String> constMap = generateConstantMap(GL.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
new Class<?>[] { glInterfaceClass },
new GLTracer(glInterface, constMap));
@ -169,7 +168,7 @@ public final class GLTracer implements InvocationHandler {
* @return A tracer that implements the given interface
*/
public static Object createDesktopGlTracer(Object glInterface, Class<?> ... glInterfaceClasses) {
IntMap<String> constMap = generateConstantMap(GL2.class, GLExt.class);
IntMap<String> constMap = generateConstantMap(GL2.class, GL3.class, GL4.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
glInterfaceClasses,
new GLTracer(glInterface, constMap));

@ -127,6 +127,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
unIndent();
startCondition(shaderNode.getCondition(), source);
source.append(nodeSource);
source.append("\n");
endCondition(shaderNode.getCondition(), source);
indent();
}

@ -421,7 +421,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
/**
* @return True if the image needs to have mipmaps generated
* for it (as requested by the texture).
* for it (as requested by the texture). This stays true even
* after mipmaps have been generated.
*/
public boolean isGeneratedMipmapsRequired() {
return needGeneratedMips;
@ -434,8 +435,9 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
@Override
public void setUpdateNeeded() {
super.setUpdateNeeded();
if (!isGeneratedMipmapsRequired() && !hasMipmaps()) {
setNeedGeneratedMipmaps();
if (isGeneratedMipmapsRequired() && !hasMipmaps()) {
// Mipmaps are no longer valid, since the image was changed.
setMipmapsGenerated(false);
}
}
@ -527,11 +529,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
this();
if (mipMapSizes != null && mipMapSizes.length <= 1) {
mipMapSizes = null;
} else {
needGeneratedMips = false;
mipsWereGenerated = true;
if (mipMapSizes != null) {
if (mipMapSizes.length <= 1) {
mipMapSizes = null;
} else {
needGeneratedMips = false;
mipsWereGenerated = true;
}
}
setFormat(format);
@ -787,8 +791,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
needGeneratedMips = false;
mipsWereGenerated = false;
} else {
needGeneratedMips = false;
mipsWereGenerated = true;
needGeneratedMips = true;
mipsWereGenerated = false;
}
setUpdateNeeded();

@ -72,17 +72,19 @@ uniform float m_Shininess;
#endif
void main(){
#ifdef NORMALMAP
mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
#if !defined(VERTEX_LIGHTING)
#if defined(NORMALMAP)
mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
if (!gl_FrontFacing)
{
tbnMat[2] = -tbnMat[2];
}
if (!gl_FrontFacing)
{
tbnMat[2] = -tbnMat[2];
}
vec3 viewDir = normalize(-vPos.xyz * tbnMat);
#else
vec3 viewDir = normalize(-vPos.xyz);
vec3 viewDir = normalize(-vPos.xyz * tbnMat);
#else
vec3 viewDir = normalize(-vPos.xyz);
#endif
#endif
vec2 newTexCoord;

@ -45,6 +45,9 @@ attribute vec3 inNormal;
#else
varying vec3 specularAccum;
varying vec4 diffuseAccum;
#ifdef COLORRAMP
uniform sampler2D m_ColorRamp;
#endif
#endif
#ifdef USE_REFLECTION
@ -160,14 +163,14 @@ void main(){
#if __VERSION__ >= 110
}
#endif
vec2 v = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess);
vec2 light = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess);
#ifdef COLORRAMP
diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor;
specularAccum += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
diffuseAccum.rgb += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor.rgb;
specularAccum.rgb += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
#else
diffuseAccum += v.x * diffuseColor;
specularAccum += v.y * specularColor;
diffuseAccum.rgb += light.x * diffuseColor.rgb;
specularAccum.rgb += light.y * specularColor;
#endif
}
#endif

@ -569,10 +569,15 @@ public class J3MLoader implements AssetLoader {
public Object load(AssetInfo info) throws IOException {
this.assetManager = info.getManager();
InputStream in = info.openStream();
try {
key = info.getKey();
key = info.getKey();
if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) {
throw new IOException("Material instances must be loaded via MaterialKey");
} else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) {
throw new IOException("Material definitions must be loaded via AssetKey");
}
loadFromRoot(BlockLanguageParser.parse(in));
} finally {
if (in != null){
@ -581,9 +586,6 @@ public class J3MLoader implements AssetLoader {
}
if (material != null){
if (!(info.getKey() instanceof MaterialKey)){
throw new IOException("Material instances must be loaded via MaterialKey");
}
// material implementation
return material;
}else{

@ -242,21 +242,12 @@ public class DXTFlipper {
img.position(blockByteOffset);
img.limit(blockByteOffset + bpb);
img.get(colorBlock);
if (type == 4 || type == 5)
flipDXT5Block(colorBlock, h);
else
flipDXT1orDXTA3Block(colorBlock, h);
// write block (no need to flip block indexes, only pixels
// inside block
retImg.put(colorBlock);
if (alphaBlock != null){
img.get(alphaBlock);
switch (type){
case 2:
flipDXT3Block(alphaBlock, h); break;
flipDXT3Block(alphaBlock, h);
break;
case 3:
case 4:
flipDXT5Block(alphaBlock, h);
@ -264,6 +255,16 @@ public class DXTFlipper {
}
retImg.put(alphaBlock);
}
img.get(colorBlock);
if (type == 4 || type == 5)
flipDXT5Block(colorBlock, h);
else
flipDXT1orDXTA3Block(colorBlock, h);
// write block (no need to flip block indexes, only pixels
// inside block
retImg.put(colorBlock);
}
retImg.rewind();
}else if (h >= 4){

@ -162,8 +162,8 @@ public class SSAOFilter extends Filter {
};
ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);
ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
// ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
// ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
postRenderPasses.add(ssaoPass);
material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md");
material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());

@ -2,3 +2,9 @@ INCLUDE com/jme3/asset/General.cfg
# IOS specific loaders
LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg
LOADER com.jme3.audio.plugins.OGGLoader : ogg
LOADER com.jme3.material.plugins.J3MLoader : j3m
LOADER com.jme3.material.plugins.J3MLoader : j3md
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
LOADER com.jme3.export.binary.BinaryImporter : j3o
LOADER com.jme3.font.plugins.BitmapFontLoader : fnt

File diff suppressed because it is too large Load Diff

@ -1,67 +0,0 @@
package com.jme3.audio.android;
import com.jme3.asset.AssetKey;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioRenderer;
import com.jme3.util.NativeObject;
public class AndroidAudioData extends AudioData {
protected AssetKey<?> assetKey;
protected float currentVolume = 0f;
public AndroidAudioData(){
super();
}
protected AndroidAudioData(int id){
super(id);
}
public AssetKey<?> getAssetKey() {
return assetKey;
}
public void setAssetKey(AssetKey<?> assetKey) {
this.assetKey = assetKey;
}
@Override
public DataType getDataType() {
return DataType.Buffer;
}
@Override
public float getDuration() {
return 0; // TODO: ???
}
@Override
public void resetObject() {
this.id = -1;
setUpdateNeeded();
}
@Override
public void deleteObject(Object rendererObject) {
((AudioRenderer)rendererObject).deleteAudioData(this);
}
public float getCurrentVolume() {
return currentVolume;
}
public void setCurrentVolume(float currentVolume) {
this.currentVolume = currentVolume;
}
@Override
public NativeObject createDestructableClone() {
return new AndroidAudioData(id);
}
@Override
public long getUniqueId() {
return ((long)OBJTYPE_AUDIOBUFFER << 32) | ((long)id);
}
}

@ -0,0 +1,53 @@
package com.jme3.audio.ios;
import com.jme3.audio.openal.AL;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public final class IosAL implements AL {
public IosAL() {
}
public native String alGetString(int parameter);
public native int alGenSources();
public native int alGetError();
public native void alDeleteSources(int numSources, IntBuffer sources);
public native void alGenBuffers(int numBuffers, IntBuffer buffers);
public native void alDeleteBuffers(int numBuffers, IntBuffer buffers);
public native void alSourceStop(int source);
public native void alSourcei(int source, int param, int value);
public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency);
public native void alSourcePlay(int source);
public native void alSourcePause(int source);
public native void alSourcef(int source, int param, float value);
public native void alSource3f(int source, int param, float value1, float value2, float value3);
public native int alGetSourcei(int source, int param);
public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers);
public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers);
public native void alListener(int param, FloatBuffer data);
public native void alListenerf(int param, float value);
public native void alListener3f(int param, float value1, float value2, float value3);
public native void alSource3i(int source, int param, int value1, int value2, int value3);
}

@ -0,0 +1,26 @@
package com.jme3.audio.ios;
import com.jme3.audio.openal.ALC;
import java.nio.IntBuffer;
public final class IosALC implements ALC {
public IosALC() {
}
public native void createALC();
public native void destroyALC();
public native boolean isCreated();
public native String alcGetString(int parameter);
public native boolean alcIsExtensionPresent(String extension);
public native void alcGetInteger(int param, IntBuffer buffer, int size);
public native void alcDevicePauseSOFT();
public native void alcDeviceResumeSOFT();
}

@ -0,0 +1,32 @@
package com.jme3.audio.ios;
import com.jme3.audio.openal.EFX;
import java.nio.IntBuffer;
public class IosEFX implements EFX {
public IosEFX() {
}
public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers);
public native void alGenEffects(int numEffects, IntBuffer buffers);
public native void alEffecti(int effect, int param, int value);
public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value);
public native void alDeleteEffects(int numEffects, IntBuffer buffers);
public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers);
public native void alGenFilters(int numFilters, IntBuffer buffers);
public native void alFilteri(int filter, int param, int value);
public native void alFilterf(int filter, int param, float value);
public native void alDeleteFilters(int numFilters, IntBuffer buffers);
public native void alEffectf(int effect, int param, float value);
}

@ -1,20 +0,0 @@
package com.jme3.audio.plugins;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader;
import com.jme3.audio.android.AndroidAudioData;
import java.io.IOException;
/**
* <code>AndroidAudioLoader</code> will create an
* {@link AndroidAudioData} object with the specified asset key.
*/
public class AndroidAudioLoader implements AssetLoader {
@Override
public Object load(AssetInfo assetInfo) throws IOException {
AndroidAudioData result = new AndroidAudioData();
result.setAssetKey(assetInfo.getKey());
return result;
}
}

@ -34,6 +34,7 @@ package com.jme3.renderer.ios;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import java.nio.Buffer;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@ -46,10 +47,13 @@ import java.nio.ShortBuffer;
*
* @author Kirill Vainer
*/
public class IosGL implements GL, GLExt {
public class IosGL implements GL, GLExt, GLFbo {
private final int[] temp_array = new int[16];
public void resetStats() {
}
private static int getLimitBytes(ByteBuffer buffer) {
checkLimit(buffer);
return buffer.limit();
@ -90,7 +94,9 @@ public class IosGL implements GL, GLExt {
if (buffer.remaining() < n) {
throw new BufferOverflowException();
}
int pos = buffer.position();
buffer.put(array, 0, n);
buffer.position(pos);
}
private static void checkLimit(Buffer buffer) {

@ -1,576 +0,0 @@
package com.jme3.renderer.ios;
//import android.graphics.Bitmap;
//import android.opengl.ETC1;
//import android.opengl.ETC1Util.ETC1Texture;
//import android.opengl.JmeIosGLES;
//import android.opengl.GLUtils;
//import com.jme3.asset.AndroidImageInfo;
import com.jme3.renderer.ios.JmeIosGLES;
import com.jme3.math.FastMath;
import com.jme3.renderer.RendererException;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TextureUtil {
private static final Logger logger = Logger.getLogger(TextureUtil.class.getName());
//TODO Make this configurable through appSettings
public static boolean ENABLE_COMPRESSION = true;
private static boolean NPOT = false;
private static boolean ETC1support = false;
private static boolean DXT1 = false;
private static boolean PVRTC = false;
private static boolean DEPTH24_STENCIL8 = false;
private static boolean DEPTH_TEXTURE = false;
private static boolean RGBA8 = false;
// Same constant used by both GL_ARM_rgba8 and GL_OES_rgb8_rgba8.
private static final int GL_RGBA8 = 0x8058;
private static final int GL_DXT1 = 0x83F0;
private static final int GL_DXT1A = 0x83F1;
private static final int GL_DEPTH_STENCIL_OES = 0x84F9;
private static final int GL_UNSIGNED_INT_24_8_OES = 0x84FA;
private static final int GL_DEPTH24_STENCIL8_OES = 0x88F0;
public static void loadTextureFeatures(String extensionString) {
ETC1support = extensionString.contains("GL_OES_compressed_ETC1_RGB8_texture");
DEPTH24_STENCIL8 = extensionString.contains("GL_OES_packed_depth_stencil");
NPOT = extensionString.contains("GL_IMG_texture_npot")
|| extensionString.contains("GL_OES_texture_npot")
|| extensionString.contains("GL_NV_texture_npot_2D_mipmap");
PVRTC = extensionString.contains("GL_IMG_texture_compression_pvrtc");
DXT1 = extensionString.contains("GL_EXT_texture_compression_dxt1");
DEPTH_TEXTURE = extensionString.contains("GL_OES_depth_texture");
RGBA8 = extensionString.contains("GL_ARM_rgba8") ||
extensionString.contains("GL_OES_rgb8_rgba8");
logger.log(Level.FINE, "Supports ETC1? {0}", ETC1support);
logger.log(Level.FINE, "Supports DEPTH24_STENCIL8? {0}", DEPTH24_STENCIL8);
logger.log(Level.FINE, "Supports NPOT? {0}", NPOT);
logger.log(Level.FINE, "Supports PVRTC? {0}", PVRTC);
logger.log(Level.FINE, "Supports DXT1? {0}", DXT1);
logger.log(Level.FINE, "Supports DEPTH_TEXTURE? {0}", DEPTH_TEXTURE);
logger.log(Level.FINE, "Supports RGBA8? {0}", RGBA8);
}
/*
private static void buildMipmap(Bitmap bitmap, boolean compress) {
int level = 0;
int height = bitmap.getHeight();
int width = bitmap.getWidth();
logger.log(Level.FINEST, " - Generating mipmaps for bitmap using SOFTWARE");
JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1);
while (height >= 1 || width >= 1) {
//First of all, generate the texture from our bitmap and set it to the according level
if (compress) {
logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) with compression.", new Object[]{level, width, height});
uploadBitmapAsCompressed(JmeIosGLES.GL_TEXTURE_2D, level, bitmap, false, 0, 0);
} else {
logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) directly.", new Object[]{level, width, height});
GLUtils.texImage2D(JmeIosGLES.GL_TEXTURE_2D, level, bitmap, 0);
}
if (height == 1 || width == 1) {
break;
}
//Increase the mipmap level
height /= 2;
width /= 2;
Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);
// Recycle any bitmaps created as a result of scaling the bitmap.
// Do not recycle the original image (mipmap level 0)
if (level != 0) {
bitmap.recycle();
}
bitmap = bitmap2;
level++;
}
}
private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap, boolean subTexture, int x, int y) {
if (bitmap.hasAlpha()) {
logger.log(Level.FINEST, " - Uploading bitmap directly. Cannot compress as alpha present.");
if (subTexture) {
GLUtils.texSubImage2D(target, level, x, y, bitmap);
JmeIosGLES.checkGLError();
} else {
GLUtils.texImage2D(target, level, bitmap, 0);
JmeIosGLES.checkGLError();
}
} else {
// Convert to RGB565
int bytesPerPixel = 2;
Bitmap rgb565 = bitmap.copy(Bitmap.Config.RGB_565, true);
// Put texture data into ByteBuffer
ByteBuffer inputImage = BufferUtils.createByteBuffer(bitmap.getRowBytes() * bitmap.getHeight());
rgb565.copyPixelsToBuffer(inputImage);
inputImage.position(0);
// Delete the copied RGB565 image
rgb565.recycle();
// Encode the image into the output bytebuffer
int encodedImageSize = ETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight());
ByteBuffer compressedImage = BufferUtils.createByteBuffer(encodedImageSize);
ETC1.encodeImage(inputImage, bitmap.getWidth(),
bitmap.getHeight(),
bytesPerPixel,
bytesPerPixel * bitmap.getWidth(),
compressedImage);
// Delete the input image buffer
BufferUtils.destroyDirectBuffer(inputImage);
// Create an ETC1Texture from the compressed image data
ETC1Texture etc1tex = new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage);
// Upload the ETC1Texture
if (bytesPerPixel == 2) {
int oldSize = (bitmap.getRowBytes() * bitmap.getHeight());
int newSize = compressedImage.capacity();
logger.log(Level.FINEST, " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}", new Object[]{oldSize, newSize, (float) oldSize / newSize});
if (subTexture) {
JmeIosGLES.glCompressedTexSubImage2D(target,
level,
x, y,
bitmap.getWidth(),
bitmap.getHeight(),
ETC1.ETC1_RGB8_OES,
etc1tex.getData().capacity(),
etc1tex.getData());
JmeIosGLES.checkGLError();
} else {
JmeIosGLES.glCompressedTexImage2D(target,
level,
ETC1.ETC1_RGB8_OES,
bitmap.getWidth(),
bitmap.getHeight(),
0,
etc1tex.getData().capacity(),
etc1tex.getData());
JmeIosGLES.checkGLError();
}
// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB,
// JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5, etc1Texture);
// } else if (bytesPerPixel == 3) {
// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB,
// JmeIosGLES.GL_UNSIGNED_BYTE, etc1Texture);
}
BufferUtils.destroyDirectBuffer(compressedImage);
}
}
/**
* <code>uploadTextureBitmap</code> uploads a native android bitmap
*/
/*
public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) {
uploadTextureBitmap(target, bitmap, needMips, false, 0, 0);
}
/**
* <code>uploadTextureBitmap</code> uploads a native android bitmap
*/
/*
public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips, boolean subTexture, int x, int y) {
boolean recycleBitmap = false;
//TODO, maybe this should raise an exception when NPOT is not supported
boolean willCompress = ENABLE_COMPRESSION && ETC1support && !bitmap.hasAlpha();
if (needMips && willCompress) {
// Image is compressed and mipmaps are desired, generate them
// using software.
buildMipmap(bitmap, willCompress);
} else {
if (willCompress) {
// Image is compressed but mipmaps are not desired, upload directly.
logger.log(Level.FINEST, " - Uploading compressed bitmap. Mipmaps are not generated.");
uploadBitmapAsCompressed(target, 0, bitmap, subTexture, x, y);
} else {
// Image is not compressed, mipmaps may or may not be desired.
logger.log(Level.FINEST, " - Uploading bitmap directly.{0}",
(needMips
? " Mipmaps will be generated in HARDWARE"
: " Mipmaps are not generated."));
if (subTexture) {
System.err.println("x : " + x + " y :" + y + " , " + bitmap.getWidth() + "/" + bitmap.getHeight());
GLUtils.texSubImage2D(target, 0, x, y, bitmap);
JmeIosGLES.checkGLError();
} else {
GLUtils.texImage2D(target, 0, bitmap, 0);
JmeIosGLES.checkGLError();
}
if (needMips) {
// No pregenerated mips available,
// generate from base level if required
JmeIosGLES.glGenerateMipmap(target);
JmeIosGLES.checkGLError();
}
}
}
if (recycleBitmap) {
bitmap.recycle();
}
}
*/
public static void uploadTextureAny(Image img, int target, int index, boolean needMips) {
/*
if (img.getEfficentData() instanceof AndroidImageInfo) {
logger.log(Level.FINEST, " === Uploading image {0}. Using BITMAP PATH === ", img);
// If image was loaded from asset manager, use fast path
AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
uploadTextureBitmap(target, imageInfo.getBitmap(), needMips);
} else {
*/
logger.log(Level.FINEST, " === Uploading image {0}. Using BUFFER PATH === ", img);
boolean wantGeneratedMips = needMips && !img.hasMipmaps();
if (wantGeneratedMips && img.getFormat().isCompressed()) {
logger.log(Level.WARNING, "Generating mipmaps is only"
+ " supported for Bitmap based or non-compressed images!");
}
// Upload using slower path
logger.log(Level.FINEST, " - Uploading bitmap directly.{0}",
(wantGeneratedMips
? " Mipmaps will be generated in HARDWARE"
: " Mipmaps are not generated."));
uploadTexture(img, target, index);
// Image was uploaded using slower path, since it is not compressed,
// then compress it
if (wantGeneratedMips) {
// No pregenerated mips available,
// generate from base level if required
JmeIosGLES.glGenerateMipmap(target);
JmeIosGLES.checkGLError();
}
//}
}
private static void unsupportedFormat(Format fmt) {
throw new UnsupportedOperationException("The image format '" + fmt + "' is unsupported by the video hardware.");
}
public static IosGLImageFormat getImageFormat(Format fmt) throws UnsupportedOperationException {
IosGLImageFormat imageFormat = new IosGLImageFormat();
switch (fmt) {
case Depth32:
case Depth32F:
throw new UnsupportedOperationException("The image format '"
+ fmt + "' is not supported by OpenGL ES 2.0 specification.");
case Alpha8:
imageFormat.format = JmeIosGLES.GL_ALPHA;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
if (RGBA8) {
imageFormat.renderBufferStorageFormat = GL_RGBA8;
} else {
// Highest precision alpha supported by vanilla OGLES2
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4;
}
break;
case Luminance8:
imageFormat.format = JmeIosGLES.GL_LUMINANCE;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
if (RGBA8) {
imageFormat.renderBufferStorageFormat = GL_RGBA8;
} else {
// Highest precision luminance supported by vanilla OGLES2
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565;
}
break;
case Luminance8Alpha8:
imageFormat.format = JmeIosGLES.GL_LUMINANCE_ALPHA;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
if (RGBA8) {
imageFormat.renderBufferStorageFormat = GL_RGBA8;
} else {
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4;
}
break;
case RGB565:
imageFormat.format = JmeIosGLES.GL_RGB;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5;
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565;
break;
case RGB5A1:
imageFormat.format = JmeIosGLES.GL_RGBA;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_5_5_1;
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB5_A1;
break;
case RGB8:
imageFormat.format = JmeIosGLES.GL_RGB;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
if (RGBA8) {
imageFormat.renderBufferStorageFormat = GL_RGBA8;
} else {
// Fallback: Use RGB565 if RGBA8 is not available.
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565;
}
break;
case BGR8:
imageFormat.format = JmeIosGLES.GL_RGB;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
if (RGBA8) {
imageFormat.renderBufferStorageFormat = GL_RGBA8;
} else {
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565;
}
break;
case RGBA8:
imageFormat.format = JmeIosGLES.GL_RGBA;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
if (RGBA8) {
imageFormat.renderBufferStorageFormat = GL_RGBA8;
} else {
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4;
}
break;
case Depth:
case Depth16:
if (!DEPTH_TEXTURE) {
unsupportedFormat(fmt);
}
imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT;
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16;
break;
case Depth24:
case Depth24Stencil8:
if (!DEPTH_TEXTURE) {
unsupportedFormat(fmt);
}
if (DEPTH24_STENCIL8) {
// NEW: True Depth24 + Stencil8 format.
imageFormat.format = GL_DEPTH_STENCIL_OES;
imageFormat.dataType = GL_UNSIGNED_INT_24_8_OES;
imageFormat.renderBufferStorageFormat = GL_DEPTH24_STENCIL8_OES;
} else {
// Vanilla OGLES2, only Depth16 available.
imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT;
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16;
}
break;
case DXT1:
if (!DXT1) {
unsupportedFormat(fmt);
}
imageFormat.format = GL_DXT1;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
imageFormat.compress = true;
break;
case DXT1A:
if (!DXT1) {
unsupportedFormat(fmt);
}
imageFormat.format = GL_DXT1A;
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE;
imageFormat.compress = true;
break;
default:
throw new UnsupportedOperationException("Unrecognized format: " + fmt);
}
return imageFormat;
}
public static class IosGLImageFormat {
boolean compress = false;
int format = -1;
int renderBufferStorageFormat = -1;
int dataType = -1;
}
private static void uploadTexture(Image img,
int target,
int index) {
/*
if (img.getEfficentData() instanceof AndroidImageInfo) {
throw new RendererException("This image uses efficient data. "
+ "Use uploadTextureBitmap instead.");
}
*/
// Otherwise upload image directly.
// Prefer to only use power of 2 textures here to avoid errors.
Image.Format fmt = img.getFormat();
ByteBuffer data;
if (index >= 0 || img.getData() != null && img.getData().size() > 0) {
data = img.getData(index);
} else {
data = null;
}
int width = img.getWidth();
int height = img.getHeight();
if (!NPOT && img.isNPOT()) {
// Check if texture is POT
throw new RendererException("Non-power-of-2 textures "
+ "are not supported by the video hardware "
+ "and no scaling path available for image: " + img);
}
IosGLImageFormat imageFormat = getImageFormat(fmt);
if (data != null) {
JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1);
JmeIosGLES.checkGLError();
}
int[] mipSizes = img.getMipMapSizes();
int pos = 0;
if (mipSizes == null) {
if (data != null) {
mipSizes = new int[]{data.capacity()};
} else {
mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8};
}
}
for (int i = 0; i < mipSizes.length; i++) {
int mipWidth = Math.max(1, width >> i);
int mipHeight = Math.max(1, height >> i);
if (data != null) {
data.position(pos);
data.limit(pos + mipSizes[i]);
}
if (imageFormat.compress && data != null) {
JmeIosGLES.glCompressedTexImage2D(target,
i,
imageFormat.format,
mipWidth,
mipHeight,
0,
data.remaining(),
data);
} else {
JmeIosGLES.glTexImage2D(target,
i,
imageFormat.format,
mipWidth,
mipHeight,
0,
imageFormat.format,
imageFormat.dataType,
data);
}
JmeIosGLES.checkGLError();
pos += mipSizes[i];
}
}
/**
* 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 img,
int target,
int index,
int x,
int y) {
//TODO:
/*
if (img.getEfficentData() instanceof AndroidImageInfo) {
AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
uploadTextureBitmap(target, imageInfo.getBitmap(), true, true, x, y);
return;
}
*/
// Otherwise upload image directly.
// Prefer to only use power of 2 textures here to avoid errors.
Image.Format fmt = img.getFormat();
ByteBuffer data;
if (index >= 0 || img.getData() != null && img.getData().size() > 0) {
data = img.getData(index);
} else {
data = null;
}
int width = img.getWidth();
int height = img.getHeight();
if (!NPOT && img.isNPOT()) {
// Check if texture is POT
throw new RendererException("Non-power-of-2 textures "
+ "are not supported by the video hardware "
+ "and no scaling path available for image: " + img);
}
IosGLImageFormat imageFormat = getImageFormat(fmt);
if (data != null) {
JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1);
JmeIosGLES.checkGLError();
}
int[] mipSizes = img.getMipMapSizes();
int pos = 0;
if (mipSizes == null) {
if (data != null) {
mipSizes = new int[]{data.capacity()};
} else {
mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8};
}
}
for (int i = 0; i < mipSizes.length; i++) {
int mipWidth = Math.max(1, width >> i);
int mipHeight = Math.max(1, height >> i);
if (data != null) {
data.position(pos);
data.limit(pos + mipSizes[i]);
}
if (imageFormat.compress && data != null) {
JmeIosGLES.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data);
JmeIosGLES.checkGLError();
} else {
JmeIosGLES.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data);
JmeIosGLES.checkGLError();
}
pos += mipSizes[i];
}
}
}

@ -40,6 +40,7 @@ import com.jme3.renderer.ios.IosGL;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLDebugES;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
@ -158,11 +159,11 @@ public class IGLESContext implements JmeContext {
GLExt glext = (GLExt) gl;
// if (settings.getBoolean("GraphicsDebug")) {
gl = new GLDebugES(gl, glext);
gl = new GLDebugES(gl, glext, (GLFbo) glext);
glext = (GLExt) gl;
// }
renderer = new GLRenderer(gl, glext);
renderer = new GLRenderer(gl, glext, (GLFbo) glext);
renderer.initialize();
input = new IosInputHandler();

@ -33,6 +33,7 @@ package com.jme3.system.ios;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.TextureKey;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import java.io.IOException;
@ -45,14 +46,16 @@ import java.io.InputStream;
public class IosImageLoader implements AssetLoader {
public Object load(AssetInfo info) throws IOException {
InputStream in = info.openStream();
boolean flip = ((TextureKey) info.getKey()).isFlipY();
Image img = null;
InputStream in = null;
try {
img = loadImageData(Image.Format.RGBA8, in);
} catch (Exception e) {
e.printStackTrace();
in = info.openStream();
img = loadImageData(Format.RGBA8, flip, in);
} finally {
in.close();
if (in != null) {
in.close();
}
}
return img;
}
@ -64,5 +67,5 @@ public class IosImageLoader implements AssetLoader {
* @param inputStream the InputStream to load the image data from
* @return the loaded Image
*/
private static native Image loadImageData(Format format, InputStream inputStream);
private static native Image loadImageData(Format format, boolean flipY, InputStream inputStream);
}

@ -36,6 +36,14 @@ import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystemDelegate;
import com.jme3.system.NullContext;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.ios.IosAL;
import com.jme3.audio.ios.IosALC;
//import com.jme3.audio.ios.IosEFX;
import com.jme3.audio.openal.AL;
import com.jme3.audio.openal.ALAudioRenderer;
import com.jme3.audio.openal.ALC;
import com.jme3.audio.openal.EFX;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
@ -89,8 +97,11 @@ public class JmeIosSystem extends JmeSystemDelegate {
@Override
public AudioRenderer newAudioRenderer(AppSettings settings) {
return null;
}
ALC alc = new IosALC();
AL al = new IosAL();
//EFX efx = new IosEFX();
return new ALAudioRenderer(al, alc, null);
}
@Override
public void initialize(AppSettings settings) {

@ -0,0 +1,10 @@
INCLUDE com/jme3/asset/General.cfg
# IOS specific loaders
LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg
LOADER com.jme3.audio.plugins.OGGLoader : ogg
LOADER com.jme3.material.plugins.J3MLoader : j3m
LOADER com.jme3.material.plugins.J3MLoader : j3md
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
LOADER com.jme3.export.binary.BinaryImporter : j3o
LOADER com.jme3.font.plugins.BitmapFontLoader : fnt

@ -13,7 +13,7 @@ import java.nio.ShortBuffer;
import com.jme3.renderer.opengl.GL4;
import org.lwjgl.opengl.*;
public class LwjglGL implements GL, GL2, GL3,GL4 {
public class LwjglGL implements GL, GL2, GL3, GL4 {
private static void checkLimit(Buffer buffer) {
if (buffer == null) {
@ -27,6 +27,9 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
}
}
public void resetStats() {
}
public void glActiveTexture(int param1) {
GL13.glActiveTexture(param1);
}
@ -237,6 +240,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
public String glGetString(int param1) {
return GL11.glGetString(param1);
}
public String glGetString(int param1, int param2) {
return GL30.glGetStringi(param1, param2);
}
public boolean glIsEnabled(int param1) {
return GL11.glIsEnabled(param1);
@ -444,4 +451,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
public void glPatchParameter(int count) {
GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count);
}
@Override
public void glDeleteVertexArrays(IntBuffer arrays) {
checkLimit(arrays);
ARBVertexArrayObject.glDeleteVertexArrays(arrays);
}
}

@ -7,12 +7,8 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBDrawInstanced;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.ARBPixelBufferObject;
import org.lwjgl.opengl.ARBSync;
import org.lwjgl.opengl.ARBTextureMultisample;
import org.lwjgl.opengl.EXTFramebufferBlit;
import org.lwjgl.opengl.EXTFramebufferMultisample;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GLSync;
@ -30,99 +26,51 @@ public class LwjglGLExt implements GLExt {
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
}
}
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override
public void glBufferData(int target, IntBuffer data, int usage) {
checkLimit(data);
GL15.glBufferData(target, data, usage);
}
@Override
public void glBufferSubData(int target, long offset, IntBuffer data) {
checkLimit(data);
GL15.glBufferSubData(target, offset, data);
}
@Override
public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount);
}
@Override
public void glDrawBuffers(IntBuffer bufs) {
checkLimit(bufs);
GL20.glDrawBuffers(bufs);
}
@Override
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) {
ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount);
}
@Override
public void glGetMultisample(int pname, int index, FloatBuffer val) {
checkLimit(val);
ARBTextureMultisample.glGetMultisample(pname, index, val);
}
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
}
@Override
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
}
@Override
public void glVertexAttribDivisorARB(int index, int divisor) {
ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor);
}
public void glBindFramebufferEXT(int param1, int param2) {
EXTFramebufferObject.glBindFramebufferEXT(param1, param2);
}
public void glBindRenderbufferEXT(int param1, int param2) {
EXTFramebufferObject.glBindRenderbufferEXT(param1, param2);
}
public int glCheckFramebufferStatusEXT(int param1) {
return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1);
}
public void glDeleteFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glDeleteFramebuffersEXT(param1);
}
public void glDeleteRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glDeleteRenderbuffersEXT(param1);
}
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4);
}
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5);
}
public void glGenFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glGenFramebuffersEXT(param1);
}
public void glGenRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glGenRenderbuffersEXT(param1);
}
public void glGenerateMipmapEXT(int param1) {
EXTFramebufferObject.glGenerateMipmapEXT(param1);
}
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4);
}
@Override
public Object glFenceSync(int condition, int flags) {
return ARBSync.glFenceSync(condition, flags);

@ -0,0 +1,98 @@
package com.jme3.renderer.lwjgl;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.opengl.GLFbo;
import java.nio.Buffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.EXTFramebufferBlit;
import org.lwjgl.opengl.EXTFramebufferMultisample;
import org.lwjgl.opengl.EXTFramebufferObject;
/**
* Implements GLFbo via GL_EXT_framebuffer_object.
*
* @author Kirill Vainer
*/
public class LwjglGLFboEXT implements GLFbo {
private static void checkLimit(Buffer buffer) {
if (buffer == null) {
return;
}
if (buffer.limit() == 0) {
throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
}
if (buffer.remaining() == 0) {
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
}
}
@Override
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
}
@Override
public void glBindFramebufferEXT(int param1, int param2) {
EXTFramebufferObject.glBindFramebufferEXT(param1, param2);
}
@Override
public void glBindRenderbufferEXT(int param1, int param2) {
EXTFramebufferObject.glBindRenderbufferEXT(param1, param2);
}
@Override
public int glCheckFramebufferStatusEXT(int param1) {
return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1);
}
@Override
public void glDeleteFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glDeleteFramebuffersEXT(param1);
}
@Override
public void glDeleteRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glDeleteRenderbuffersEXT(param1);
}
@Override
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4);
}
@Override
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5);
}
@Override
public void glGenFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glGenFramebuffersEXT(param1);
}
@Override
public void glGenRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glGenRenderbuffersEXT(param1);
}
@Override
public void glGenerateMipmapEXT(int param1) {
EXTFramebufferObject.glGenerateMipmapEXT(param1);
}
@Override
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4);
}
}

@ -0,0 +1,96 @@
package com.jme3.renderer.lwjgl;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.opengl.GLFbo;
import java.nio.Buffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.GL30;
/**
* Implements GLFbo via OpenGL3+.
*
* @author Kirill Vainer
*/
public class LwjglGLFboGL3 implements GLFbo {
private static void checkLimit(Buffer buffer) {
if (buffer == null) {
return;
}
if (buffer.limit() == 0) {
throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
}
if (buffer.remaining() == 0) {
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
}
}
@Override
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
}
@Override
public void glBindFramebufferEXT(int param1, int param2) {
GL30.glBindFramebuffer(param1, param2);
}
@Override
public void glBindRenderbufferEXT(int param1, int param2) {
GL30.glBindRenderbuffer(param1, param2);
}
@Override
public int glCheckFramebufferStatusEXT(int param1) {
return GL30.glCheckFramebufferStatus(param1);
}
@Override
public void glDeleteFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
GL30.glDeleteFramebuffers(param1);
}
@Override
public void glDeleteRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
GL30.glDeleteRenderbuffers(param1);
}
@Override
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
GL30.glFramebufferRenderbuffer(param1, param2, param3, param4);
}
@Override
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
GL30.glFramebufferTexture2D(param1, param2, param3, param4, param5);
}
@Override
public void glGenFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
GL30.glGenFramebuffers(param1);
}
@Override
public void glGenRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
GL30.glGenRenderbuffers(param1);
}
@Override
public void glGenerateMipmapEXT(int param1) {
GL30.glGenerateMipmap(param1);
}
@Override
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
GL30.glRenderbufferStorage(param1, param2, param3, param4);
}
}

@ -39,13 +39,18 @@ import com.jme3.renderer.Renderer;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.lwjgl.LwjglGL;
import com.jme3.renderer.lwjgl.LwjglGLExt;
import com.jme3.renderer.lwjgl.LwjglGLFboEXT;
import com.jme3.renderer.lwjgl.LwjglGLFboGL3;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GL2;
import com.jme3.renderer.opengl.GL3;
import com.jme3.renderer.opengl.GL4;
import com.jme3.renderer.opengl.GLDebugDesktop;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.opengl.GLTiming;
import com.jme3.renderer.opengl.GLTimingState;
import com.jme3.renderer.opengl.GLTracer;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
@ -203,28 +208,44 @@ public abstract class LwjglContext implements JmeContext {
}
if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
|| settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
|| settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
GL gl = new LwjglGL();
GLFbo glfbo = new LwjglGLExt();
GLExt glext = new LwjglGLExt();
GLFbo glfbo;
if (GLContext.getCapabilities().OpenGL30) {
glfbo = new LwjglGLFboGL3();
} else {
glfbo = new LwjglGLFboEXT();
}
if (settings.getBoolean("GraphicsDebug")) {
gl = new GLDebugDesktop(gl, glfbo);
gl = new GLDebugDesktop(gl, glext, glfbo);
glext = (GLExt) gl;
glfbo = (GLFbo) gl;
}
if (settings.getBoolean("GraphicsTiming")) {
GLTimingState timingState = new GLTimingState();
gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
}
if (settings.getBoolean("GraphicsTrace")) {
gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class);
glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLExt.class);
gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
}
renderer = new GLRenderer(gl, glfbo);
renderer = new GLRenderer(gl, glext, glfbo);
renderer.initialize();
} else {
throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
}
if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback());
ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler()));
}
renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());

@ -0,0 +1,78 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.system.lwjgl;
import java.util.HashMap;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
class LwjglGLDebugOutputHandler implements ARBDebugOutputCallback.Handler {
private static final HashMap<Integer, String> constMap = new HashMap<Integer, String>();
private static final String MESSAGE_FORMAT =
"[JME3] OpenGL debug message\r\n" +
" ID: %d\r\n" +
" Source: %s\r\n" +
" Type: %s\r\n" +
" Severity: %s\r\n" +
" Message: %s";
static {
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB, "API");
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_APPLICATION_ARB, "APPLICATION");
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_OTHER_ARB, "OTHER");
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, "SHADER_COMPILER");
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_THIRD_PARTY_ARB, "THIRD_PARTY");
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB, "WINDOW_SYSTEM");
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB, "DEPRECATED_BEHAVIOR");
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_ERROR_ARB, "ERROR");
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB, "OTHER");
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PERFORMANCE_ARB, "PERFORMANCE");
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PORTABILITY_ARB, "PORTABILITY");
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB, "UNDEFINED_BEHAVIOR");
constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB, "HIGH");
constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB, "MEDIUM");
constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, "LOW");
}
@Override
public void handleMessage(int source, int type, int id, int severity, String message) {
String sourceStr = constMap.get(source);
String typeStr = constMap.get(type);
String severityStr = constMap.get(severity);
System.err.println(String.format(MESSAGE_FORMAT, id, sourceStr, typeStr, severityStr, message));
}
}

@ -31,6 +31,8 @@
*/
package com.jme3.network;
import com.jme3.network.service.ClientServiceManager;
/**
* Represents a remote connection to a server that can be used
@ -72,6 +74,12 @@ public interface Client extends MessageConnection
* be able to connect to.
*/
public int getVersion();
/**
* Returns the manager for client services. Client services extend
* the functionality of the client.
*/
public ClientServiceManager getServices();
/**
* Sends a message to the server.

@ -33,6 +33,8 @@ package com.jme3.network;
import java.util.Collection;
import com.jme3.network.service.HostedServiceManager;
/**
* Represents a host that can send and receive messages to
* a set of remote client connections.
@ -54,6 +56,12 @@ public interface Server
*/
public int getVersion();
/**
* Returns the manager for hosted services. Hosted services extend
* the functionality of the server.
*/
public HostedServiceManager getServices();
/**
* Sends the specified message to all connected clients.
*/

@ -37,6 +37,8 @@ import com.jme3.network.kernel.Connector;
import com.jme3.network.message.ChannelInfoMessage;
import com.jme3.network.message.ClientRegistrationMessage;
import com.jme3.network.message.DisconnectMessage;
import com.jme3.network.service.ClientServiceManager;
import com.jme3.network.service.serializer.ClientSerializerRegistrationsService;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -54,7 +56,7 @@ import java.util.logging.Logger;
*/
public class DefaultClient implements Client
{
static Logger log = Logger.getLogger(DefaultClient.class.getName());
static final Logger log = Logger.getLogger(DefaultClient.class.getName());
// First two channels are reserved for reliable and
// unreliable. Note: channels are endpoint specific so these
@ -80,10 +82,14 @@ public class DefaultClient implements Client
private ConnectorFactory connectorFactory;
private ClientServiceManager services;
public DefaultClient( String gameName, int version )
{
this.gameName = gameName;
this.version = version;
this.services = new ClientServiceManager(this);
addStandardServices();
}
public DefaultClient( String gameName, int version, Connector reliable, Connector fast,
@ -93,6 +99,10 @@ public class DefaultClient implements Client
setPrimaryConnectors( reliable, fast, connectorFactory );
}
protected void addStandardServices() {
services.addService(new ClientSerializerRegistrationsService());
}
protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory )
{
if( reliable == null )
@ -200,6 +210,11 @@ public class DefaultClient implements Client
{
return version;
}
public ClientServiceManager getServices()
{
return services;
}
public void send( Message message )
{
@ -260,7 +275,7 @@ public class DefaultClient implements Client
{
checkRunning();
closeConnections( null );
closeConnections( null );
}
protected void closeConnections( DisconnectInfo info )
@ -268,6 +283,10 @@ public class DefaultClient implements Client
if( !isRunning )
return;
// Let the services get a chance to stop before we
// kill the connection.
services.stop();
// Send a close message
// Tell the thread it's ok to die
@ -285,6 +304,9 @@ public class DefaultClient implements Client
fireDisconnected(info);
isRunning = false;
// Terminate the services
services.terminate();
}
public void addClientStateListener( ClientStateListener listener )
@ -329,6 +351,9 @@ public class DefaultClient implements Client
protected void fireConnected()
{
// Let the services know we are finally started
services.start();
for( ClientStateListener l : stateListeners ) {
l.clientConnected( this );
}

@ -37,6 +37,8 @@ import com.jme3.network.kernel.Kernel;
import com.jme3.network.message.ChannelInfoMessage;
import com.jme3.network.message.ClientRegistrationMessage;
import com.jme3.network.message.DisconnectMessage;
import com.jme3.network.service.HostedServiceManager;
import com.jme3.network.service.serializer.ServerSerializerRegistrationsService;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -55,7 +57,7 @@ import java.util.logging.Logger;
*/
public class DefaultServer implements Server
{
static Logger log = Logger.getLogger(DefaultServer.class.getName());
static final Logger log = Logger.getLogger(DefaultServer.class.getName());
// First two channels are reserved for reliable and
// unreliable
@ -85,6 +87,8 @@ public class DefaultServer implements Server
= new MessageListenerRegistry<HostedConnection>();
private List<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<ConnectionListener>();
private HostedServiceManager services;
public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast )
{
if( reliable == null )
@ -92,6 +96,8 @@ public class DefaultServer implements Server
this.gameName = gameName;
this.version = version;
this.services = new HostedServiceManager(this);
addStandardServices();
reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true );
channels.add( reliableAdapter );
@ -101,6 +107,10 @@ public class DefaultServer implements Server
}
}
protected void addStandardServices() {
services.addService(new ServerSerializerRegistrationsService());
}
public String getGameName()
{
return gameName;
@ -110,6 +120,11 @@ public class DefaultServer implements Server
{
return version;
}
public HostedServiceManager getServices()
{
return services;
}
public int addChannel( int port )
{
@ -164,7 +179,10 @@ public class DefaultServer implements Server
ka.start();
}
isRunning = true;
isRunning = true;
// Start the services
services.start();
}
public boolean isRunning()
@ -177,13 +195,20 @@ public class DefaultServer implements Server
if( !isRunning )
throw new IllegalStateException( "Server is not started." );
// First stop the services since we are about to
// kill the connections they are using
services.stop();
try {
// Kill the adpaters, they will kill the kernels
for( KernelAdapter ka : channels ) {
ka.close();
}
isRunning = false;
isRunning = false;
// Now terminate all of the services
services.terminate();
} catch( InterruptedException e ) {
throw new RuntimeException( "Interrupted while closing", e );
}
@ -396,6 +421,18 @@ public class DefaultServer implements Server
return endpointConnections.get(endpoint);
}
protected void removeConnecting( Endpoint p )
{
// No easy lookup for connecting Connections
// from endpoint.
for( Map.Entry<Long,Connection> e : connecting.entrySet() ) {
if( e.getValue().hasEndpoint(p) ) {
connecting.remove(e.getKey());
return;
}
}
}
protected void connectionClosed( Endpoint p )
{
if( p.isConnected() ) {
@ -411,10 +448,10 @@ public class DefaultServer implements Server
// Also note: this method will be called multiple times per
// HostedConnection if it has multiple endpoints.
Connection removed = null;
Connection removed;
synchronized( this ) {
// Just in case the endpoint was still connecting
connecting.values().remove(p);
removeConnecting(p);
// And the regular management
removed = (Connection)endpointConnections.remove(p);
@ -452,6 +489,16 @@ public class DefaultServer implements Server
id = nextId.getAndIncrement();
channels = new Endpoint[channelCount];
}
boolean hasEndpoint( Endpoint p )
{
for( Endpoint e : channels ) {
if( p == e ) {
return true;
}
}
return false;
}
void setChannel( int channel, Endpoint p )
{
@ -557,6 +604,7 @@ public class DefaultServer implements Server
return Collections.unmodifiableSet(sessionData.keySet());
}
@Override
public String toString()
{
return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE]

@ -0,0 +1,188 @@
/*
* $Id: SerializerRegistrationsMessage.java 3829 2014-11-24 07:25:43Z pspeed $
*
* Copyright (c) 2012, Paul Speed
* All rights reserved.
*/
package com.jme3.network.message;
import com.jme3.network.AbstractMessage;
import com.jme3.network.serializing.Serializable;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.serializing.SerializerRegistration;
import com.jme3.network.serializing.serializers.FieldSerializer;
import java.util.*;
import java.util.jar.Attributes;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Holds a compiled set of message registration information that
* can be sent over the wire. The received message can then be
* used to register all of the classes using the same IDs and
* same ordering, etc.. The intent is that the server compiles
* this message once it is sure that all serializable classes have
* been registered. It can then send this to each new client and
* they can use it to register all of the classes without requiring
* exactly reproducing the same calls that the server did to register
* messages.
*
* <p>Normally, JME recommends that apps have a common utility method
* that they call on both client and server. However, this makes
* pluggable services nearly impossible as some central class has to
* know about all registered serializers. This message implementation
* gets around by only requiring registration on the server.</p>
*
* @author Paul Speed
*/
@Serializable
public class SerializerRegistrationsMessage extends AbstractMessage {
static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName());
public static final Set<Class> ignore = new HashSet<Class>();
static {
// We could build this automatically but then we
// risk making a client and server out of date simply because
// their JME versions are out of date.
ignore.add(Boolean.class);
ignore.add(Float.class);
ignore.add(Boolean.class);
ignore.add(Byte.class);
ignore.add(Character.class);
ignore.add(Short.class);
ignore.add(Integer.class);
ignore.add(Long.class);
ignore.add(Float.class);
ignore.add(Double.class);
ignore.add(String.class);
ignore.add(DisconnectMessage.class);
ignore.add(ClientRegistrationMessage.class);
ignore.add(Date.class);
ignore.add(AbstractCollection.class);
ignore.add(AbstractList.class);
ignore.add(AbstractSet.class);
ignore.add(ArrayList.class);
ignore.add(HashSet.class);
ignore.add(LinkedHashSet.class);
ignore.add(LinkedList.class);
ignore.add(TreeSet.class);
ignore.add(Vector.class);
ignore.add(AbstractMap.class);
ignore.add(Attributes.class);
ignore.add(HashMap.class);
ignore.add(Hashtable.class);
ignore.add(IdentityHashMap.class);
ignore.add(TreeMap.class);
ignore.add(WeakHashMap.class);
ignore.add(Enum.class);
ignore.add(GZIPCompressedMessage.class);
ignore.add(ZIPCompressedMessage.class);
ignore.add(ChannelInfoMessage.class);
ignore.add(SerializerRegistrationsMessage.class);
ignore.add(SerializerRegistrationsMessage.Registration.class);
}
public static SerializerRegistrationsMessage INSTANCE;
public static Registration[] compiled;
private static final Serializer fieldSerializer = new FieldSerializer();
private Registration[] registrations;
public SerializerRegistrationsMessage() {
setReliable(true);
}
public SerializerRegistrationsMessage( Registration... registrations ) {
setReliable(true);
this.registrations = registrations;
}
public static void compile() {
// Let's just see what they are here
List<Registration> list = new ArrayList<Registration>();
for( SerializerRegistration reg : Serializer.getSerializerRegistrations() ) {
Class type = reg.getType();
if( ignore.contains(type) )
continue;
if( type.isPrimitive() )
continue;
list.add(new Registration(reg));
}
if( log.isLoggable(Level.FINE) ) {
log.log( Level.FINE, "Number of registered classes:{0}", list.size());
for( Registration reg : list ) {
log.log( Level.FINE, " {0}", reg);
}
}
compiled = list.toArray(new Registration[list.size()]);
INSTANCE = new SerializerRegistrationsMessage(compiled);
}
public void registerAll() {
for( Registration reg : registrations ) {
log.log( Level.INFO, "Registering:{0}", reg);
reg.register();
}
}
@Serializable
public static final class Registration {
private short id;
private String className;
private String serializerClassName;
public Registration() {
}
public Registration( SerializerRegistration reg ) {
this.id = reg.getId();
this.className = reg.getType().getName();
if( reg.getSerializer().getClass() != FieldSerializer.class ) {
this.serializerClassName = reg.getSerializer().getClass().getName();
}
}
public void register() {
try {
Class type = Class.forName(className);
Serializer serializer;
if( serializerClassName == null ) {
serializer = fieldSerializer;
} else {
Class serializerType = Class.forName(serializerClassName);
serializer = (Serializer)serializerType.newInstance();
}
SerializerRegistration result = Serializer.registerClassForId(id, type, serializer);
log.log( Level.FINE, " result:{0}", result);
} catch( ClassNotFoundException e ) {
throw new RuntimeException( "Class not found attempting to register:" + this, e );
} catch( InstantiationException e ) {
throw new RuntimeException( "Error instantiating serializer registering:" + this, e );
} catch( IllegalAccessException e ) {
throw new RuntimeException( "Error instantiating serializer registering:" + this, e );
}
}
@Override
public String toString() {
return "Registration[" + id + " = " + className + ", serializer=" + serializerClassName + "]";
}
}
}

@ -0,0 +1,51 @@
/*
* Copyright (c) 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.network.service;
/**
* Convenient base class for ClientServices providing some default ClientService
* interface implementations as well as a few convenience methods
* such as getServiceManager() and getService(type). Subclasses
* must at least override the onInitialize() method to handle
* service initialization.
*
* @author Paul Speed
*/
public abstract class AbstractClientService extends AbstractService<ClientServiceManager>
implements ClientService {
protected AbstractClientService() {
}
}

@ -0,0 +1,70 @@
/*
* Copyright (c) 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.network.service;
import com.jme3.network.HostedConnection;
import com.jme3.network.Server;
/**
* Convenient base class for HostedServices providing some default HostedService
* interface implementations as well as a few convenience methods
* such as getServiceManager() and getService(type). Subclasses
* must at least override the onInitialize() method to handle
* service initialization.
*
* @author Paul Speed
*/
public abstract class AbstractHostedService extends AbstractService<HostedServiceManager>
implements HostedService {
protected AbstractHostedService() {
}
/**
* Default implementation does nothing. Implementations can
* override this to peform custom new connection behavior.
*/
@Override
public void connectionAdded(Server server, HostedConnection hc) {
}
/**
* Default implementation does nothing. Implementations can
* override this to peform custom leaving connection behavior.
*/
@Override
public void connectionRemoved(Server server, HostedConnection hc) {
}
}

@ -0,0 +1,111 @@
/*
* Copyright (c) 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.network.service;
/**
* Base class providing some default Service interface implementations
* as well as a few convenience methods such as getServiceManager()
* and getService(type). Subclasses must at least override the
* onInitialize() method to handle service initialization.
*
* @author Paul Speed
*/
public abstract class AbstractService<S extends ServiceManager> implements Service<S> {
private S serviceManager;
protected AbstractService() {
}
/**
* Returns the ServiceManager that was passed to
* initialize() during service initialization.
*/
protected S getServiceManager() {
return serviceManager;
}
/**
* Retrieves the first sibling service of the specified
* type.
*/
protected <T extends Service<S>> T getService( Class<T> type ) {
return type.cast(serviceManager.getService(type));
}
/**
* Initializes this service by keeping a reference to
* the service manager and calling onInitialize().
*/
@Override
public final void initialize( S serviceManager ) {
this.serviceManager = serviceManager;
onInitialize(serviceManager);
}
/**
* Called during initialize() for the subclass to perform
* implementation specific initialization.
*/
protected abstract void onInitialize( S serviceManager );
/**
* Default implementation does nothing. Implementations can
* override this to peform custom startup behavior.
*/
@Override
public void start() {
}
/**
* Default implementation does nothing. Implementations can
* override this to peform custom stop behavior.
*/
@Override
public void stop() {
}
/**
* Default implementation does nothing. Implementations can
* override this to peform custom termination behavior.
*/
@Override
public void terminate( S serviceManager ) {
}
@Override
public String toString() {
return getClass().getName() + "[serviceManager=" + serviceManager + "]";
}
}

@ -0,0 +1,72 @@
/*
* Copyright (c) 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.network.service;
/**
* Interface implemented by Client-side services that augment
* a network Client's functionality.
*
* @author Paul Speed
*/
public interface ClientService extends Service<ClientServiceManager> {
/**
* Called when the service is first attached to the service
* manager.
*/
@Override
public void initialize( ClientServiceManager serviceManager );
/**
* Called when the service manager is started or if the
* service is added to an already started service manager.
*/
@Override
public void start();
/**
* Called when the service is shutting down. All services
* are stopped and any service manager resources are closed
* before the services are terminated.
*/
@Override
public void stop();
/**
* The service manager is fully shutting down. All services
* have been stopped and related connections closed.
*/
@Override
public void terminate( ClientServiceManager serviceManager );
}

@ -0,0 +1,100 @@
/*
* Copyright (c) 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.network.service;
import com.jme3.network.Client;
/**
* Manages ClientServices on behalf of a network Client object.
*
* @author Paul Speed
*/
public class ClientServiceManager extends ServiceManager<ClientServiceManager> {
private Client client;
/**
* Creates a new ClientServiceManager for the specified network Client.
*/
public ClientServiceManager( Client client ) {
this.client = client;
}
/**
* Returns the network Client associated with this ClientServiceManager.
*/
public Client getClient() {
return client;
}
/**
* Returns 'this' and is what is passed to ClientService.initialize()
* and ClientService.termnate();
*/
@Override
protected final ClientServiceManager getParent() {
return this;
}
/**
* Adds the specified ClientService and initializes it. If the service manager
* has already been started then the service will also be started.
*/
public void addService( ClientService s ) {
super.addService(s);
}
/**
* Adds all of the specified ClientServices and initializes them. If the service manager
* has already been started then the services will also be started.
* This is a convenience method that delegates to addService(), thus each
* service will be initialized (and possibly started) in sequence rather
* than doing them all at the end.
*/
public void addServices( ClientService... services ) {
for( ClientService s : services ) {
super.addService(s);
}
}
/**
* Removes the specified ClientService from this service manager, stopping
* and terminating it as required. If this service manager is in a
* started state then the service will be stopped. After removal,
* the service will be terminated.
*/
public void removeService( ClientService s ) {
super.removeService(s);
}
}

@ -0,0 +1,74 @@
/*
* Copyright (c) 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.network.service;
import com.jme3.network.ConnectionListener;
/**
* Interface implemented by Server-side services that augment
* a network Server's functionality.
*
* @author Paul Speed
*/
public interface HostedService extends Service<HostedServiceManager>, ConnectionListener {
/**
* Called when the service is first attached to the service
* manager.
*/
@Override
public void initialize( HostedServiceManager serviceManager );
/**
* Called when the service manager is started or if the
* service is added to an already started service manager.
*/
@Override
public void start();
/**
* Called when the service is shutting down. All services
* are stopped and any service manager resources are closed
* before the services are terminated.
*/
@Override
public void stop();
/**
* The service manager is fully shutting down. All services
* have been stopped and related connections closed.
*/
@Override
public void terminate( HostedServiceManager serviceManager );
}

@ -0,0 +1,140 @@
/*
* Copyright (c) 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.network.service;
import com.jme3.network.ConnectionListener;
import com.jme3.network.HostedConnection;
import com.jme3.network.Server;
/**
* Manages HostedServices on behalf of a network Server object.
* All HostedServices are automatically informed about new and
* leaving connections.
*
* @author Paul Speed
*/
public class HostedServiceManager extends ServiceManager<HostedServiceManager> {
private Server server;
private ConnectionObserver connectionObserver;
/**
* Creates a HostedServiceManager for the specified network Server.
*/
public HostedServiceManager( Server server ) {
this.server = server;
this.connectionObserver = new ConnectionObserver();
server.addConnectionListener(connectionObserver);
}
/**
* Returns the network Server associated with this HostedServiceManager.
*/
public Server getServer() {
return server;
}
/**
* Returns 'this' and is what is passed to HostedService.initialize()
* and HostedService.termnate();
*/
@Override
protected final HostedServiceManager getParent() {
return this;
}
/**
* Adds the specified HostedService and initializes it. If the service manager
* has already been started then the service will also be started.
*/
public void addService( HostedService s ) {
super.addService(s);
}
/**
* Adds all of the specified HostedServices and initializes them. If the service manager
* has already been started then the services will also be started.
* This is a convenience method that delegates to addService(), thus each
* service will be initialized (and possibly started) in sequence rather
* than doing them all at the end.
*/
public void addServices( HostedService... services ) {
for( HostedService s : services ) {
super.addService(s);
}
}
/**
* Removes the specified HostedService from this service manager, stopping
* and terminating it as required. If this service manager is in a
* started state then the service will be stopped. After removal,
* the service will be terminated.
*/
public void removeService( HostedService s ) {
super.removeService(s);
}
/**
* Called internally when a new connection has been added so that the
* services can be notified.
*/
protected void addConnection( HostedConnection hc ) {
for( Service s : getServices() ) {
((HostedService)s).connectionAdded(server, hc);
}
}
/**
* Called internally when a connection has been removed so that the
* services can be notified.
*/
protected void removeConnection( HostedConnection hc ) {
for( Service s : getServices() ) {
((HostedService)s).connectionRemoved(server, hc);
}
}
protected class ConnectionObserver implements ConnectionListener {
@Override
public void connectionAdded(Server server, HostedConnection hc) {
addConnection(hc);
}
@Override
public void connectionRemoved(Server server, HostedConnection hc) {
removeConnection(hc);
}
}
}

@ -0,0 +1,67 @@
/*
* Copyright (c) 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.network.service;
/**
* The base interface for managed services.
*
* @author Paul Speed
*/
public interface Service<S> {
/**
* Called when the service is first attached to the service
* manager.
*/
public void initialize( S serviceManager );
/**
* Called when the service manager is started or if the
* service is added to an already started service manager.
*/
public void start();
/**
* Called when the service manager is shutting down. All services
* are stopped and any service manager resources are closed
* before the services are terminated.
*/
public void stop();
/**
* The service manager is fully shutting down. All services
* have been stopped and related connections closed.
*/
public void terminate( S serviceManager );
}

@ -0,0 +1,160 @@
/*
* Copyright (c) 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.network.service;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The base service manager class from which the HostedServiceManager
* and ClientServiceManager classes are derived. This manages the
* the underlying services and their life cycles.
*
* @author Paul Speed
*/
public abstract class ServiceManager<T> {
private List<Service<T>> services = new CopyOnWriteArrayList<Service<T>>();
private volatile boolean started = false;
protected ServiceManager() {
}
/**
* Retreives the 'parent' of this service manager, usually
* a more specifically typed version of 'this' but it can be
* anything the seervices are expecting.
*/
protected abstract T getParent();
/**
* Returns the complete list of services managed by this
* service manager. This list is thread safe following the
* CopyOnWriteArrayList semantics.
*/
protected List<Service<T>> getServices() {
return services;
}
/**
* Starts this service manager and all services that it contains.
* Any services added after the service manager has started will have
* their start() methods called.
*/
public void start() {
if( started ) {
return;
}
for( Service<T> s : services ) {
s.start();
}
started = true;
}
/**
* Returns true if this service manager has been started.
*/
public boolean isStarted() {
return started;
}
/**
* Stops all services and puts the service manager into a stopped state.
*/
public void stop() {
if( !started ) {
throw new IllegalStateException(getClass().getSimpleName() + " not started.");
}
for( Service<T> s : services ) {
s.stop();
}
started = false;
}
/**
* Adds the specified service and initializes it. If the service manager
* has already been started then the service will also be started.
*/
public <S extends Service<T>> void addService( S s ) {
services.add(s);
s.initialize(getParent());
if( started ) {
s.start();
}
}
/**
* Removes the specified service from this service manager, stopping
* and terminating it as required. If this service manager is in a
* started state then the service will be stopped. After removal,
* the service will be terminated.
*/
public <S extends Service<T>> void removeService( S s ) {
if( started ) {
s.stop();
}
services.remove(s);
s.terminate(getParent());
}
/**
* Terminates all services. If the service manager has not been
* stopped yet then it will be stopped.
*/
public void terminate() {
if( started ) {
stop();
}
for( Service<T> s : services ) {
s.terminate(getParent());
}
}
/**
* Retrieves the first service of the specified type.
*/
public <S extends Service<T>> S getService( Class<S> type ) {
for( Service s : services ) {
if( type.isInstance(s) ) {
return type.cast(s);
}
}
return null;
}
@Override
public String toString() {
return getClass().getName() + "[services=" + services + "]";
}
}

@ -0,0 +1,123 @@
/*
* Copyright (c) 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.network.service.rpc;
import com.jme3.network.Client;
import com.jme3.network.util.ObjectMessageDelegator;
import com.jme3.network.service.AbstractClientService;
import com.jme3.network.service.ClientServiceManager;
/**
* RPC service that can be added to a network Client to
* add RPC send/receive capabilities. Remote procedure
* calls can be made to the server and responses retrieved.
* Any remote procedure calls that the server performs for
* this connection will be received by this service and delegated
* to the appropriate RpcHandlers.
*
* @author Paul Speed
*/
public class RpcClientService extends AbstractClientService {
private RpcConnection rpc;
private ObjectMessageDelegator delegator;
/**
* Creates a new RpcClientService that can be registered
* with the network Client object.
*/
public RpcClientService() {
}
/**
* Used internally to setup the RpcConnection and MessageDelegator.
*/
@Override
protected void onInitialize( ClientServiceManager serviceManager ) {
Client client = serviceManager.getClient();
this.rpc = new RpcConnection(client);
delegator = new ObjectMessageDelegator(rpc, true);
client.addMessageListener(delegator, delegator.getMessageTypes());
}
/**
* Used internally to unregister the RPC MessageDelegator that
* was previously added to the network Client.
*/
@Override
public void terminate( ClientServiceManager serviceManager ) {
Client client = serviceManager.getClient();
client.removeMessageListener(delegator, delegator.getMessageTypes());
}
/**
* Performs a synchronous call on the server against the specified
* object using the specified procedure ID. Both inboud and outbound
* communication is done on the specified channel.
*/
public Object callAndWait( byte channel, short objId, short procId, Object... args ) {
return rpc.callAndWait(channel, objId, procId, args);
}
/**
* Performs an asynchronous call on the server against the specified
* object using the specified procedure ID. Communication is done
* over the specified channel. No responses are received and none
* are waited for.
*/
public void callAsync( byte channel, short objId, short procId, Object... args ) {
rpc.callAsync(channel, objId, procId, args);
}
/**
* Register a handler that will be called when the server
* performs a remove procedure call against this client.
* Only one handler per object ID can be registered at any given time,
* though the same handler can be registered for multiple object
* IDs.
*/
public void registerHandler( short objId, RpcHandler handler ) {
rpc.registerHandler(objId, handler);
}
/**
* Removes a previously registered handler for the specified
* object ID.
*/
public void removeHandler( short objId, RpcHandler handler ) {
rpc.removeHandler(objId, handler);
}
}

@ -0,0 +1,260 @@
/*
* Copyright (c) 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.network.service.rpc;
import com.jme3.network.MessageConnection;
import com.jme3.network.service.rpc.msg.RpcCallMessage;
import com.jme3.network.service.rpc.msg.RpcResponseMessage;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Wraps a message connection to provide RPC call support. This
* is used internally by the RpcClientService and RpcHostedService to manage
* network messaging.
*
* @author Paul Speed
*/
public class RpcConnection {
static final Logger log = Logger.getLogger(RpcConnection.class.getName());
/**
* The underlying connection upon which RPC call messages are sent
* and RPC response messages are received. It can be a Client or
* a HostedConnection depending on the mode of the RPC service.
*/
private MessageConnection connection;
/**
* The objectId index of RpcHandler objects that are used to perform the
* RPC calls for a particular object.
*/
private Map<Short, RpcHandler> handlers = new ConcurrentHashMap<Short, RpcHandler>();
/**
* Provides unique messages IDs for outbound synchronous call
* messages. These are then used in the responses index to
* locate the proper ResponseHolder objects.
*/
private AtomicLong sequenceNumber = new AtomicLong();
/**
* Tracks the ResponseHolder objects for sent message IDs. When the
* response is received, the appropriate handler is found here and the
* response or error set, thus releasing the waiting caller.
*/
private Map<Long, ResponseHolder> responses = new ConcurrentHashMap<Long, ResponseHolder>();
/**
* Creates a new RpcConnection for the specified network connection.
*/
public RpcConnection( MessageConnection connection ) {
this.connection = connection;
}
/**
* Clears any pending synchronous calls causing them to
* throw an exception with the message "Closing connection".
*/
public void close() {
// Let any pending waits go free
for( ResponseHolder holder : responses.values() ) {
holder.release();
}
}
/**
* Performs a remote procedure call with the specified arguments and waits
* for the response. Both the outbound message and inbound response will
* be sent on the specified channel.
*/
public Object callAndWait( byte channel, short objId, short procId, Object... args ) {
RpcCallMessage msg = new RpcCallMessage(sequenceNumber.getAndIncrement(),
channel, objId, procId, args);
// Need to register an object so we can wait for the response.
// ...before we send it. Just in case.
ResponseHolder holder = new ResponseHolder(msg);
responses.put(msg.getMessageId(), holder);
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel});
}
if( channel >= 0 ) {
connection.send(channel, msg);
} else {
connection.send(msg);
}
return holder.getResponse();
}
/**
* Performs a remote procedure call with the specified arguments but does
* not wait for a response. The outbound message is sent on the specified channel.
* There is no inbound response message.
*/
public void callAsync( byte channel, short objId, short procId, Object... args ) {
RpcCallMessage msg = new RpcCallMessage(-1, channel, objId, procId, args);
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel});
}
connection.send(channel, msg);
}
/**
* Register a handler that can be called by the other end
* of the connection using the specified object ID. Only one
* handler per object ID can be registered at any given time,
* though the same handler can be registered for multiple object
* IDs.
*/
public void registerHandler( short objId, RpcHandler handler ) {
handlers.put(objId, handler);
}
/**
* Removes a previously registered handler for the specified
* object ID.
*/
public void removeHandler( short objId, RpcHandler handler ) {
RpcHandler removing = handlers.get(objId);
if( handler != removing ) {
throw new IllegalArgumentException("Handler not registered for object ID:"
+ objId + ", handler:" + handler );
}
handlers.remove(objId);
}
protected void send( byte channel, RpcResponseMessage msg ) {
if( channel >= 0 ) {
connection.send(channel, msg);
} else {
connection.send(msg);
}
}
/**
* Called internally when an RpcCallMessage is received from
* the remote connection.
*/
public void handleMessage( RpcCallMessage msg ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "handleMessage({0})", msg);
}
RpcHandler handler = handlers.get(msg.getObjectId());
try {
if( handler == null ) {
throw new RuntimeException("Handler not found for objectID:" + msg.getObjectId());
}
Object result = handler.call(this, msg.getObjectId(), msg.getProcedureId(), msg.getArguments());
if( !msg.isAsync() ) {
send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), result));
}
} catch( Exception e ) {
if( !msg.isAsync() ) {
send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), e));
} else {
log.log(Level.SEVERE, "Error invoking async call for:" + msg, e);
}
}
}
/**
* Called internally when an RpcResponseMessage is received from
* the remote connection.
*/
public void handleMessage( RpcResponseMessage msg ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "handleMessage({0})", msg);
}
ResponseHolder holder = responses.remove(msg.getMessageId());
if( holder == null ) {
return;
}
holder.setResponse(msg);
}
/**
* Sort of like a Future, holds a locked reference to a response
* until the remote call has completed and returned a response.
*/
private class ResponseHolder {
private Object response;
private String error;
private RpcCallMessage msg;
boolean received = false;
public ResponseHolder( RpcCallMessage msg ) {
this.msg = msg;
}
public synchronized void setResponse( RpcResponseMessage msg ) {
this.response = msg.getResult();
this.error = msg.getError();
this.received = true;
notifyAll();
}
public synchronized Object getResponse() {
try {
while(!received) {
wait();
}
} catch( InterruptedException e ) {
throw new RuntimeException("Interrupted waiting for respone to:" + msg, e);
}
if( error != null ) {
throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error);
}
return response;
}
public synchronized void release() {
if( received ) {
return;
}
// Else signal an error for the callers
this.error = "Closing connection";
this.received = true;
}
}
}

@ -0,0 +1,52 @@
/*
* Copyright (c) 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.network.service.rpc;
/**
* Implementations of this interface can be registered with
* the RpcClientService or RpcHostService to handle the
* remote procedure calls for a given object or objects.
*
* @author Paul Speed
*/
public interface RpcHandler {
/**
* Called when a remote procedure call request is received for a particular
* object from the other end of the network connection.
*/
public Object call( RpcConnection conn, short objectId, short procId, Object... args );
}

@ -0,0 +1,227 @@
/*
* Copyright (c) 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.network.service.rpc;
import com.jme3.network.HostedConnection;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.util.SessionDataDelegator;
import com.jme3.network.service.AbstractHostedService;
import com.jme3.network.service.HostedServiceManager;
import com.jme3.network.service.rpc.msg.RpcCallMessage;
import com.jme3.network.service.rpc.msg.RpcResponseMessage;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* RPC service that can be added to a network Server to
* add RPC send/receive capabilities. For a particular
* HostedConnection, Remote procedure calls can be made to the
* associated Client and responses retrieved. Any remote procedure
* calls that the Client performs for this connection will be
* received by this service and delegated to the appropriate RpcHandlers.
*
* Note: it can be dangerous for a server to perform synchronous
* RPC calls to a client but especially so if not done as part
* of the response to some other message. ie: iterating over all
* or some HostedConnections to perform synchronous RPC calls
* will be slow and potentially block the server's threads in ways
* that can cause deadlocks or odd contention.
*
* @author Paul Speed
*/
public class RpcHostedService extends AbstractHostedService {
private static final String ATTRIBUTE_NAME = "rpcSession";
static final Logger log = Logger.getLogger(RpcHostedService.class.getName());
private boolean autoHost;
private SessionDataDelegator delegator;
/**
* Creates a new RPC host service that can be registered
* with the Network server and will automatically 'host'
* RPC services and each new network connection.
*/
public RpcHostedService() {
this(true);
}
/**
* Creates a new RPC host service that can be registered
* with the Network server and will optionally 'host'
* RPC services and each new network connection depending
* on the specified 'autoHost' flag.
*/
public RpcHostedService( boolean autoHost ) {
this.autoHost = autoHost;
// This works for me... has to be different in
// the general case
Serializer.registerClasses(RpcCallMessage.class, RpcResponseMessage.class);
}
/**
* Used internally to setup the message delegator that will
* handle HostedConnection specific messages and forward them
* to that connection's RpcConnection.
*/
@Override
protected void onInitialize( HostedServiceManager serviceManager ) {
Server server = serviceManager.getServer();
// A general listener for forwarding the messages
// to the client-specific handler
this.delegator = new SessionDataDelegator(RpcConnection.class,
ATTRIBUTE_NAME,
true);
server.addMessageListener(delegator, delegator.getMessageTypes());
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "Registered delegator for message types:{0}", Arrays.asList(delegator.getMessageTypes()));
}
}
/**
* When set to true, all new connections will automatically have
* RPC hosting services attached to them, meaning they can send
* and receive RPC calls. If this is set to false then it is up
* to other services to eventually call startHostingOnConnection().
*
* <p>Reasons for doing this vary but usually would be because
* the client shouldn't be allowed to perform any RPC calls until
* it has provided more information. In general, this is unnecessary
* because the RpcHandler registries are not shared. Each client
* gets their own and RPC calls will fail until the appropriate
* objects have been registtered.</p>
*/
public void setAutoHost( boolean b ) {
this.autoHost = b;
}
/**
* Returns true if this service automatically attaches RPC
* hosting capabilities to new connections.
*/
public boolean getAutoHost() {
return autoHost;
}
/**
* Retrieves the RpcConnection for the specified HostedConnection
* if that HostedConnection has had RPC services started using
* startHostingOnConnection() (or via autohosting). Returns null
* if the connection currently doesn't have RPC hosting services
* attached.
*/
public RpcConnection getRpcConnection( HostedConnection hc ) {
return hc.getAttribute(ATTRIBUTE_NAME);
}
/**
* Sets up RPC hosting services for the hosted connection allowing
* getRpcConnection() to return a valid RPC connection object.
* This method is called automatically for all new connections if
* autohost is set to true.
*/
public void startHostingOnConnection( HostedConnection hc ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "startHostingOnConnection:{0}", hc);
}
hc.setAttribute(ATTRIBUTE_NAME, new RpcConnection(hc));
}
/**
* Removes any RPC hosting services associated with the specified
* connection. Calls to getRpcConnection() will return null for
* this connection. The connection's RpcConnection is also closed,
* releasing any waiting synchronous calls with a "Connection closing"
* error.
* This method is called automatically for all leaving connections if
* autohost is set to true.
*/
public void stopHostingOnConnection( HostedConnection hc ) {
RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME);
if( rpc == null ) {
return;
}
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc);
}
hc.setAttribute(ATTRIBUTE_NAME, null);
rpc.close();
}
/**
* Used internally to remove the message delegator from the
* server.
*/
@Override
public void terminate(HostedServiceManager serviceManager) {
Server server = serviceManager.getServer();
server.removeMessageListener(delegator, delegator.getMessageTypes());
}
/**
* Called internally when a new connection is detected for
* the server. If the current autoHost property is true then
* startHostingOnConnection(hc) is called.
*/
@Override
public void connectionAdded(Server server, HostedConnection hc) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc});
}
if( autoHost ) {
startHostingOnConnection(hc);
}
}
/**
* Called internally when an existing connection is leaving
* the server. If the current autoHost property is true then
* stopHostingOnConnection(hc) is called.
*/
@Override
public void connectionRemoved(Server server, HostedConnection hc) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc});
}
stopHostingOnConnection(hc);
}
}

@ -0,0 +1,98 @@
/*
* Copyright (c) 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.network.service.rpc.msg;
import com.jme3.network.AbstractMessage;
import com.jme3.network.serializing.Serializable;
/**
* Used internally to send RPC call information to
* the other end of a connection for execution.
*
* @author Paul Speed
*/
@Serializable
public class RpcCallMessage extends AbstractMessage {
private long msgId;
private byte channel;
private short objId;
private short procId;
private Object[] args;
public RpcCallMessage() {
}
public RpcCallMessage( long msgId, byte channel, short objId, short procId, Object... args ) {
this.msgId = msgId;
this.channel = channel;
this.objId = objId;
this.procId = procId;
this.args = args;
}
public long getMessageId() {
return msgId;
}
public byte getChannel() {
return channel;
}
public boolean isAsync() {
return msgId == -1;
}
public short getObjectId() {
return objId;
}
public short getProcedureId() {
return procId;
}
public Object[] getArguments() {
return args;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[#" + msgId + ", channel=" + channel
+ (isAsync() ? ", async" : ", sync")
+ ", objId=" + objId
+ ", procId=" + procId
+ ", args.length=" + args.length
+ "]";
}
}

@ -0,0 +1,89 @@
/*
* Copyright (c) 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.network.service.rpc.msg;
import com.jme3.network.AbstractMessage;
import com.jme3.network.serializing.Serializable;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Used internally to send an RPC call's response back to
* the caller.
*
* @author Paul Speed
*/
@Serializable
public class RpcResponseMessage extends AbstractMessage {
private long msgId;
private Object result;
private String error;
public RpcResponseMessage() {
}
public RpcResponseMessage( long msgId, Object result ) {
this.msgId = msgId;
this.result = result;
}
public RpcResponseMessage( long msgId, Throwable t ) {
this.msgId = msgId;
StringWriter sOut = new StringWriter();
PrintWriter out = new PrintWriter(sOut);
t.printStackTrace(out);
out.close();
this.error = sOut.toString();
}
public long getMessageId() {
return msgId;
}
public Object getResult() {
return result;
}
public String getError() {
return error;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[#" + msgId + ", result=" + result
+ "]";
}
}

@ -0,0 +1,69 @@
/*
* Copyright (c) 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.network.service.serializer;
import com.jme3.network.Client;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.message.SerializerRegistrationsMessage;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.service.AbstractClientService;
import com.jme3.network.service.ClientServiceManager;
/**
*
*
* @author Paul Speed
*/
public class ClientSerializerRegistrationsService extends AbstractClientService
implements MessageListener<Client> {
@Override
protected void onInitialize( ClientServiceManager serviceManager ) {
// Make sure our message type is registered
// This is the minimum we'd need just to be able to register
// the rest... otherwise we can't even receive this message.
Serializer.registerClass(SerializerRegistrationsMessage.class);
Serializer.registerClass(SerializerRegistrationsMessage.Registration.class);
// Add our listener for that message type
serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class);
}
public void messageReceived( Client source, Message m ) {
// We only wait for one kind of message...
SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m;
msg.registerAll();
}
}

@ -0,0 +1,72 @@
/*
* Copyright (c) 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.network.service.serializer;
import com.jme3.network.HostedConnection;
import com.jme3.network.Server;
import com.jme3.network.message.SerializerRegistrationsMessage;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.service.AbstractHostedService;
import com.jme3.network.service.HostedServiceManager;
/**
*
*
* @author Paul Speed
*/
public class ServerSerializerRegistrationsService extends AbstractHostedService {
@Override
protected void onInitialize( HostedServiceManager serviceManager ) {
// Make sure our message type is registered
Serializer.registerClass(SerializerRegistrationsMessage.class);
Serializer.registerClass(SerializerRegistrationsMessage.Registration.class);
}
@Override
public void start() {
// Compile the registrations into a message we will
// send to all connecting clients
SerializerRegistrationsMessage.compile();
}
@Override
public void connectionAdded(Server server, HostedConnection hc) {
// Just in case
super.connectionAdded(server, hc);
// Send the client the registration information
hc.send(SerializerRegistrationsMessage.INSTANCE);
}
}

@ -0,0 +1,314 @@
/*
* Copyright (c) 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.network.util;
import com.jme3.network.Message;
import com.jme3.network.MessageConnection;
import com.jme3.network.MessageListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A MessageListener implementation that will forward messages to methods
* of a delegate object. These methods can be automapped or manually
* specified. Subclasses provide specific implementations for how to
* find the actual delegate object.
*
* @author Paul Speed
*/
public abstract class AbstractMessageDelegator<S extends MessageConnection>
implements MessageListener<S> {
static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName());
private Class delegateType;
private Map<Class, Method> methods = new HashMap<Class, Method>();
private Class[] messageTypes;
/**
* Creates an AbstractMessageDelegator that will forward received
* messages to methods of the specified delegate type. If automap
* is true then reflection is used to lookup probably message handling
* methods.
*/
protected AbstractMessageDelegator( Class delegateType, boolean automap ) {
this.delegateType = delegateType;
if( automap ) {
automap();
}
}
/**
* Returns the array of messages known to be handled by this message
* delegator.
*/
public Class[] getMessageTypes() {
if( messageTypes == null ) {
messageTypes = methods.keySet().toArray(new Class[methods.size()]);
}
return messageTypes;
}
/**
* Returns true if the specified method is valid for the specified
* message type. This is used internally during automapping to
* provide implementation specific filting of methods.
* This implementation checks for methods that take either the connection and message
* type arguments (in that order) or just the message type.
*/
protected boolean isValidMethod( Method m, Class messageType ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "isValidMethod({0}, {1})", new Object[]{m, messageType});
}
// Parameters must be S and message type or just message type
Class<?>[] parms = m.getParameterTypes();
if( parms.length != 2 && parms.length != 1 ) {
log.finest("Parameter count is not 1 or 2");
return false;
}
int messageIndex = 0;
if( parms.length > 1 ) {
if( MessageConnection.class.isAssignableFrom(parms[0]) ) {
messageIndex++;
} else {
log.finest("First paramter is not a MessageConnection or subclass.");
return false;
}
}
if( messageType == null && !Message.class.isAssignableFrom(parms[messageIndex]) ) {
log.finest("Second paramter is not a Message or subclass.");
return false;
}
if( messageType != null && !parms[messageIndex].isAssignableFrom(messageType) ) {
log.log(Level.FINEST, "Second paramter is not a {0}", messageType);
return false;
}
return true;
}
/**
* Convenience method that returns the message type as
* reflecively determined for a particular method. This
* only works with methods that actually have arguments.
* This implementation returns the last element of the method's
* getParameterTypes() array, thus supporting both
* method(connection, messageType) as well as just method(messageType)
* calling forms.
*/
protected Class getMessageType( Method m ) {
Class<?>[] parms = m.getParameterTypes();
return parms[parms.length-1];
}
/**
* Goes through all of the delegate type's methods to find
* a method of the specified name that may take the specified
* message type.
*/
protected Method findDelegate( String name, Class messageType ) {
// We do an exhaustive search because it's easier to
// check for a variety of parameter types and it's all
// that Class would be doing in getMethod() anyway.
for( Method m : delegateType.getDeclaredMethods() ) {
if( !m.getName().equals(name) ) {
continue;
}
if( isValidMethod(m, messageType) ) {
return m;
}
}
return null;
}
/**
* Returns true if the specified method name is allowed.
* This is used by automapping to determine if a method
* should be rejected purely on name. Default implemention
* always returns true.
*/
protected boolean allowName( String name ) {
return true;
}
/**
* Calls the map(Set) method with a null argument causing
* all available matching methods to mapped to message types.
*/
protected final void automap() {
map((Set<String>)null);
if( methods.isEmpty() ) {
throw new RuntimeException("No message handling methods found for class:" + delegateType);
}
}
/**
* Specifically maps the specified methods names, autowiring
* the parameters.
*/
public AbstractMessageDelegator<S> map( String... methodNames ) {
Set<String> names = new HashSet<String>( Arrays.asList(methodNames) );
map(names);
return this;
}
/**
* Goes through all of the delegate type's declared methods
* mapping methods that match the current constraints.
* If the constraints set is null then allowName() is
* checked for names otherwise only names in the constraints
* set are allowed.
* For each candidate method that passes the above checks,
* isValidMethod() is called with a null message type argument.
* All methods are made accessible thus supporting non-public
* methods as well as public methods.
*/
protected void map( Set<String> constraints ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "map({0})", constraints);
}
for( Method m : delegateType.getDeclaredMethods() ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "Checking method:{0}", m);
}
if( constraints == null && !allowName(m.getName()) ) {
log.finest("Name is not allowed.");
continue;
}
if( constraints != null && !constraints.contains(m.getName()) ) {
log.finest("Name is not in constraints set.");
continue;
}
if( isValidMethod(m, null) ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{getMessageType(m), m});
}
// Make sure we can access the method even if it's not public or
// is in a non-public inner class.
m.setAccessible(true);
methods.put(getMessageType(m), m);
}
}
messageTypes = null;
}
/**
* Manually maps a specified method to the specified message type.
*/
public AbstractMessageDelegator<S> map( Class messageType, String methodName ) {
// Lookup the method
Method m = findDelegate( methodName, messageType );
if( m == null ) {
throw new RuntimeException( "Method:" + methodName
+ " not found matching signature (MessageConnection, "
+ messageType.getName() + ")" );
}
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{messageType, m});
}
methods.put( messageType, m );
messageTypes = null;
return this;
}
/**
* Returns the mapped method for the specified message type.
*/
protected Method getMethod( Class c ) {
Method m = methods.get(c);
return m;
}
/**
* Implemented by subclasses to provide the actual delegate object
* against which the mapped message type methods will be called.
*/
protected abstract Object getSourceDelegate( S source );
/**
* Implementation of the MessageListener's messageReceived()
* method that will use the current message type mapping to
* find an appropriate message handling method and call it
* on the delegate returned by getSourceDelegate().
*/
@Override
public void messageReceived( S source, Message msg ) {
if( msg == null ) {
return;
}
Object delegate = getSourceDelegate(source);
if( delegate == null ) {
// Means ignore this message/source
return;
}
Method m = getMethod(msg.getClass());
if( m == null ) {
throw new RuntimeException("Delegate method not found for message class:"
+ msg.getClass());
}
try {
if( m.getParameterTypes().length > 1 ) {
m.invoke( delegate, source, msg );
} else {
m.invoke( delegate, msg );
}
} catch( IllegalAccessException e ) {
throw new RuntimeException("Error executing:" + m, e);
} catch( InvocationTargetException e ) {
throw new RuntimeException("Error executing:" + m, e.getCause());
}
}
}

@ -0,0 +1,72 @@
/*
* Copyright (c) 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.network.util;
import com.jme3.network.MessageConnection;
/**
* A MessageListener implementation that will forward messages to methods
* of a specified delegate object. These methods can be automapped or manually
* specified.
*
* @author Paul Speed
*/
public class ObjectMessageDelegator<S extends MessageConnection> extends AbstractMessageDelegator<S> {
private Object delegate;
/**
* Creates a MessageListener that will forward mapped message types
* to methods of the specified object.
* If automap is true then all methods with the proper signature will
* be mapped.
* <p>Methods of the following signatures are allowed:
* <ul>
* <li>void someName(S conn, SomeMessage msg)
* <li>void someName(Message msg)
* </ul>
* Where S is the type of MessageConnection and SomeMessage is some
* specific concreate Message subclass.
*/
public ObjectMessageDelegator( Object delegate, boolean automap ) {
super(delegate.getClass(), automap);
this.delegate = delegate;
}
@Override
protected Object getSourceDelegate( MessageConnection source ) {
return delegate;
}
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 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.network.util;
import com.jme3.network.HostedConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A MessageListener implementation that will forward messages to methods
* of a delegate specified as a HostedConnection session attribute. This is
* useful for handling connection-specific messages from clients that must
* delegate to client-specific data objects.
* The delegate methods can be automapped or manually specified.
*
* @author Paul Speed
*/
public class SessionDataDelegator extends AbstractMessageDelegator<HostedConnection> {
static final Logger log = Logger.getLogger(SessionDataDelegator.class.getName());
private String attributeName;
/**
* Creates a MessageListener that will forward mapped message types
* to methods of an object specified as a HostedConnection attribute.
* If automap is true then all methods with the proper signature will
* be mapped.
* <p>Methods of the following signatures are allowed:
* <ul>
* <li>void someName(S conn, SomeMessage msg)
* <li>void someName(Message msg)
* </ul>
* Where S is the type of MessageConnection and SomeMessage is some
* specific concreate Message subclass.
*/
public SessionDataDelegator( Class delegateType, String attributeName, boolean automap ) {
super(delegateType, automap);
this.attributeName = attributeName;
}
/**
* Returns the attribute name that will be used to look up the
* delegate object.
*/
public String getAttributeName() {
return attributeName;
}
/**
* Called internally when there is no session object
* for the current attribute name attached to the passed source
* HostConnection. Default implementation logs a warning.
*/
protected void miss( HostedConnection source ) {
log.log(Level.WARNING, "Session data is null for:{0} on connection:{1}", new Object[]{attributeName, source});
}
/**
* Returns the attributeName attribute of the supplied source
* HostConnection. If there is no value at that attribute then
* the miss() method is called.
*/
protected Object getSourceDelegate( HostedConnection source ) {
Object result = source.getAttribute(attributeName);
if( result == null ) {
miss(source);
}
return result;
}
}

@ -32,6 +32,8 @@
package com.jme3.scene.plugins.fbx;
import com.jme3.asset.TextureKey;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
@ -56,6 +58,13 @@ public class ContentTextureKey extends TextureKey {
return content;
}
@Override
public Class<? extends AssetCache> getCacheType(){
// Need to override this so that textures embedded in FBX
// don't get cached by the asset manager.
return null;
}
@Override
public String toString() {
return super.toString() + " " + content.length + " bytes";

@ -0,0 +1,413 @@
/*
* 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;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation;
import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.Track;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.AssetManager;
import com.jme3.asset.ModelKey;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.fbx.anim.FbxToJmeTrack;
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode;
import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer;
import com.jme3.scene.plugins.fbx.anim.FbxAnimStack;
import com.jme3.scene.plugins.fbx.anim.FbxBindPose;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.file.FbxDump;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FbxFile;
import com.jme3.scene.plugins.fbx.file.FbxReader;
import com.jme3.scene.plugins.fbx.file.FbxId;
import com.jme3.scene.plugins.fbx.misc.FbxGlobalSettings;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.node.FbxRootNode;
import com.jme3.scene.plugins.fbx.obj.FbxObjectFactory;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxLoader implements AssetLoader {
private static final Logger logger = Logger.getLogger(FbxLoader.class.getName());
private AssetManager assetManager;
private String sceneName;
private String sceneFilename;
private String sceneFolderName;
private FbxGlobalSettings globalSettings;
private final Map<FbxId, FbxObject> objectMap = new HashMap<FbxId, FbxObject>();
private final List<FbxAnimStack> animStacks = new ArrayList<FbxAnimStack>();
private final List<FbxBindPose> bindPoses = new ArrayList<FbxBindPose>();
@Override
public Object load(AssetInfo assetInfo) throws IOException {
this.assetManager = assetInfo.getManager();
AssetKey<?> assetKey = assetInfo.getKey();
if (!(assetKey instanceof ModelKey)) {
throw new AssetLoadException("Invalid asset key");
}
InputStream stream = assetInfo.openStream();
try {
sceneFilename = assetKey.getName();
sceneFolderName = assetKey.getFolder();
String ext = assetKey.getExtension();
sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1);
if (sceneFolderName != null && sceneFolderName.length() > 0) {
sceneName = sceneName.substring(sceneFolderName.length());
}
reset();
// Load the data from the stream.
loadData(stream);
// Bind poses are needed to compute world transforms.
applyBindPoses();
// Need world transforms for skeleton creation.
updateWorldTransforms();
// Need skeletons for meshs to be created in scene graph construction.
// Mesh bone indices require skeletons to determine bone index.
constructSkeletons();
// Create the jME3 scene graph from the FBX scene graph.
// Also creates SkeletonControls based on the constructed skeletons.
Spatial scene = constructSceneGraph();
// Load animations into AnimControls
constructAnimations();
return scene;
} finally {
releaseObjects();
if (stream != null) {
stream.close();
}
}
}
private void reset() {
globalSettings = new FbxGlobalSettings();
}
private void releaseObjects() {
globalSettings = null;
objectMap.clear();
animStacks.clear();
}
private void loadData(InputStream stream) throws IOException {
FbxFile scene = FbxReader.readFBX(stream);
FbxDump.dumpFile(scene);
// TODO: Load FBX object templates
for (FbxElement e : scene.rootElements) {
if (e.id.equals("FBXHeaderExtension")) {
loadHeader(e);
} else if (e.id.equals("GlobalSettings")) {
loadGlobalSettings(e);
} else if (e.id.equals("Objects")) {
loadObjects(e);
} else if (e.id.equals("Connections")) {
connectObjects(e);
}
}
}
private void loadHeader(FbxElement element) {
for (FbxElement e : element.children) {
if (e.id.equals("FBXVersion")) {
Integer version = (Integer) e.properties.get(0);
if (version < 7100) {
logger.log(Level.WARNING, "FBX file version is older than 7.1. "
+ "Some features may not work.");
}
}
}
}
private void loadGlobalSettings(FbxElement element) {
globalSettings = new FbxGlobalSettings();
globalSettings.fromElement(element);
}
private void loadObjects(FbxElement element) {
// Initialize the FBX root element.
objectMap.put(FbxId.ROOT, new FbxRootNode(assetManager, sceneFolderName));
for(FbxElement e : element.children) {
if (e.id.equals("GlobalSettings")) {
// Old FBX files seem to have the GlobalSettings element
// under Objects (??)
globalSettings.fromElement(e);
} else {
FbxObject object = FbxObjectFactory.createObject(e, assetManager, sceneFolderName);
if (object != null) {
if (objectMap.containsKey(object.getId())) {
logger.log(Level.WARNING, "An object with ID \"{0}\" has "
+ "already been defined. "
+ "Ignoring.",
object.getId());
}
objectMap.put(object.getId(), object);
if (object instanceof FbxAnimStack) {
// NOTE: animation stacks are implicitly global.
// Capture them here.
animStacks.add((FbxAnimStack) object);
} else if (object instanceof FbxBindPose) {
bindPoses.add((FbxBindPose) object);
}
} else {
throw new UnsupportedOperationException("Failed to create FBX object of type: " + e.id);
}
}
}
}
private void removeUnconnectedObjects() {
for (FbxObject object : new ArrayList<FbxObject>(objectMap.values())) {
if (!object.isJmeObjectCreated()) {
logger.log(Level.WARNING, "Purging orphan FBX object: {0}", object);
objectMap.remove(object.getId());
}
}
}
private void connectObjects(FbxElement element) {
if (objectMap.isEmpty()) {
logger.log(Level.WARNING, "FBX file is missing object information");
return;
} else if (objectMap.size() == 1) {
// Only root node (automatically added by jME3)
logger.log(Level.WARNING, "FBX file has no objects");
return;
}
for (FbxElement el : element.children) {
if (!el.id.equals("C") && !el.id.equals("Connect")) {
continue;
}
String type = (String) el.properties.get(0);
FbxId childId;
FbxId parentId;
if (type.equals("OO")) {
childId = FbxId.create(el.properties.get(1));
parentId = FbxId.create(el.properties.get(2));
FbxObject child = objectMap.get(childId);
FbxObject parent;
if (parentId.isNull()) {
// TODO: maybe clean this up a bit..
parent = objectMap.get(FbxId.ROOT);
} else {
parent = objectMap.get(parentId);
}
if (parent == null) {
throw new UnsupportedOperationException("Cannot find parent object ID \"" + parentId + "\"");
}
parent.connectObject(child);
} else if (type.equals("OP")) {
childId = FbxId.create(el.properties.get(1));
parentId = FbxId.create(el.properties.get(2));
String propName = (String) el.properties.get(3);
FbxObject child = objectMap.get(childId);
FbxObject parent = objectMap.get(parentId);
parent.connectObjectProperty(child, propName);
} else {
logger.log(Level.WARNING, "Unknown connection type: {0}. Ignoring.", type);
}
}
}
/**
* Copies the bind poses from FBX BindPose objects to FBX nodes.
* Must be called prior to {@link #updateWorldTransforms()}.
*/
private void applyBindPoses() {
for (FbxBindPose bindPose : bindPoses) {
Map<FbxId, Matrix4f> bindPoseData = bindPose.getJmeObject();
logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size());
for (Map.Entry<FbxId, Matrix4f> entry : bindPoseData.entrySet()) {
FbxObject obj = objectMap.get(entry.getKey());
if (obj instanceof FbxNode) {
FbxNode node = (FbxNode) obj;
node.setWorldBindPose(entry.getValue());
} else {
logger.log(Level.WARNING, "Bind pose can only be applied to FBX nodes. Ignoring.");
}
}
}
}
/**
* Updates world transforms and bind poses for the FBX scene graph.
*/
private void updateWorldTransforms() {
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
fbxRoot.updateWorldTransforms(null, null);
}
private void constructAnimations() {
// In FBX, animation are not attached to any root.
// They are implicitly global.
// So, we need to use hueristics to find which node(s)
// an animation is associated with, so we can create the AnimControl
// in the appropriate location in the scene.
Map<FbxToJmeTrack, FbxToJmeTrack> pairs = new HashMap<FbxToJmeTrack, FbxToJmeTrack>();
for (FbxAnimStack stack : animStacks) {
for (FbxAnimLayer layer : stack.getLayers()) {
for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) {
for (Map.Entry<FbxNode, String> nodePropertyEntry : curveNode.getInfluencedNodeProperties().entrySet()) {
FbxToJmeTrack lookupPair = new FbxToJmeTrack();
lookupPair.animStack = stack;
lookupPair.animLayer = layer;
lookupPair.node = nodePropertyEntry.getKey();
// Find if this pair is already stored
FbxToJmeTrack storedPair = pairs.get(lookupPair);
if (storedPair == null) {
// If not, store it.
storedPair = lookupPair;
pairs.put(storedPair, storedPair);
}
String property = nodePropertyEntry.getValue();
storedPair.animCurves.put(property, curveNode);
}
}
}
}
// At this point we can construct the animation for all pairs ...
for (FbxToJmeTrack pair : pairs.values()) {
String animName = pair.animStack.getName();
float duration = pair.animStack.getDuration();
System.out.println("ANIMATION: " + animName + ", duration = " + duration);
System.out.println("NODE: " + pair.node.getName());
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);
} else {
// Create the spatial animation
Animation anim = new Animation(animName, duration);
anim.setTracks(new Track[]{ pair.toJmeSpatialTrack() });
// Get the animation control (create if missing).
Spatial jmeSpatial = pair.node.getJmeObject();
AnimControl animControl = jmeSpatial.getControl(AnimControl.class);
if (animControl == null) {
animControl = new AnimControl(null);
jmeSpatial.addControl(animControl);
}
// Add the spatial animation
animControl.addAnim(anim);
}
}
}
private void constructSkeletons() {
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
FbxNode.createSkeletons(fbxRoot);
}
private Spatial constructSceneGraph() {
// Acquire the implicit root object.
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
// Convert it into a jME3 scene
Node jmeRoot = (Node) FbxNode.createScene(fbxRoot);
// Fix the name (will probably be set to something like "-node")
jmeRoot.setName(sceneName + "-scene");
return jmeRoot;
}
}

@ -73,9 +73,9 @@ import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval;
import com.jme3.scene.plugins.fbx.file.FBXElement;
import com.jme3.scene.plugins.fbx.file.FBXFile;
import com.jme3.scene.plugins.fbx.file.FBXReader;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FbxFile;
import com.jme3.scene.plugins.fbx.file.FbxReader;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
@ -165,8 +165,8 @@ public class SceneLoader implements AssetLoader {
private void loadScene(InputStream stream) throws IOException {
logger.log(Level.FINE, "Loading scene {0}", sceneFilename);
long startTime = System.currentTimeMillis();
FBXFile scene = FBXReader.readFBX(stream);
for(FBXElement e : scene.rootElements) {
FbxFile scene = FbxReader.readFBX(stream);
for(FbxElement e : scene.rootElements) {
if(e.id.equals("GlobalSettings"))
loadGlobalSettings(e);
else if(e.id.equals("Objects"))
@ -178,10 +178,10 @@ public class SceneLoader implements AssetLoader {
logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime);
}
private void loadGlobalSettings(FBXElement element) {
for(FBXElement e : element.children) {
private void loadGlobalSettings(FbxElement element) {
for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("UnitScaleFactor"))
@ -197,8 +197,8 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadObjects(FBXElement element) {
for(FBXElement e : element.children) {
private void loadObjects(FbxElement element) {
for(FbxElement e : element.children) {
if(e.id.equals("Geometry"))
loadGeometry(e);
else if(e.id.equals("Material"))
@ -222,12 +222,12 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadGeometry(FBXElement element) {
private void loadGeometry(FbxElement element) {
long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2);
if(type.equals("Mesh")) {
MeshData data = new MeshData();
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("Vertices"))
data.vertices = (double[]) e.properties.get(0);
else if(e.id.equals("PolygonVertexIndex"))
@ -236,7 +236,7 @@ public class SceneLoader implements AssetLoader {
//else if(e.id.equals("Edges"))
// data.edges = (int[]) e.properties.get(0);
else if(e.id.equals("LayerElementNormal"))
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.normalsMapping = (String) e2.properties.get(0);
if(!data.normalsMapping.equals("ByVertice") && !data.normalsMapping.equals("ByPolygonVertex"))
@ -249,7 +249,7 @@ public class SceneLoader implements AssetLoader {
data.normals = (double[]) e2.properties.get(0);
}
else if(e.id.equals("LayerElementTangent"))
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.tangentsMapping = (String) e2.properties.get(0);
if(!data.tangentsMapping.equals("ByVertice") && !data.tangentsMapping.equals("ByPolygonVertex"))
@ -262,7 +262,7 @@ public class SceneLoader implements AssetLoader {
data.tangents = (double[]) e2.properties.get(0);
}
else if(e.id.equals("LayerElementBinormal"))
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.binormalsMapping = (String) e2.properties.get(0);
if(!data.binormalsMapping.equals("ByVertice") && !data.binormalsMapping.equals("ByPolygonVertex"))
@ -275,7 +275,7 @@ public class SceneLoader implements AssetLoader {
data.binormals = (double[]) e2.properties.get(0);
}
else if(e.id.equals("LayerElementUV"))
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.uvMapping = (String) e2.properties.get(0);
if(!data.uvMapping.equals("ByPolygonVertex"))
@ -291,7 +291,7 @@ public class SceneLoader implements AssetLoader {
}
// TODO smoothing is not used now
//else if(e.id.equals("LayerElementSmoothing"))
// for(FBXElement e2 : e.children) {
// for(FbxElement e2 : e.children) {
// if(e2.id.equals("MappingInformationType")) {
// data.smoothingMapping = (String) e2.properties.get(0);
// if(!data.smoothingMapping.equals("ByEdge"))
@ -304,7 +304,7 @@ public class SceneLoader implements AssetLoader {
// data.smoothing = (int[]) e2.properties.get(0);
// }
else if(e.id.equals("LayerElementMaterial"))
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.materialsMapping = (String) e2.properties.get(0);
if(!data.materialsMapping.equals("AllSame"))
@ -321,18 +321,18 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadMaterial(FBXElement element) {
private void loadMaterial(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("")) {
MaterialData data = new MaterialData();
data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("ShadingModel")) {
data.shadingModel = (String) e.properties.get(0);
} else if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("AmbientColor")) {
@ -368,16 +368,16 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadModel(FBXElement element) {
private void loadModel(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
ModelData data = new ModelData();
data.name = path.substring(0, path.indexOf(0));
data.type = type;
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("Lcl Translation")) {
@ -408,17 +408,17 @@ public class SceneLoader implements AssetLoader {
modelDataMap.put(id, data);
}
private void loadPose(FBXElement element) {
private void loadPose(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("BindPose")) {
BindPoseData data = new BindPoseData();
data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("PoseNode")) {
NodeTransformData item = new NodeTransformData();
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("Node"))
item.nodeId = (Long) e2.properties.get(0);
else if(e2.id.equals("Matrix"))
@ -431,14 +431,14 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadTexture(FBXElement element) {
private void loadTexture(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("")) {
TextureData data = new TextureData();
data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("Type"))
data.bindType = (String) e.properties.get(0);
else if(e.id.equals("FileName"))
@ -448,14 +448,14 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadImage(FBXElement element) {
private void loadImage(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("Clip")) {
ImageData data = new ImageData();
data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("Type"))
data.type = (String) e.properties.get(0);
else if(e.id.equals("FileName"))
@ -471,19 +471,19 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadDeformer(FBXElement element) {
private void loadDeformer(FbxElement element) {
long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2);
if(type.equals("Skin")) {
SkinData skinData = new SkinData();
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("SkinningType"))
skinData.type = (String) e.properties.get(0);
}
skinMap.put(id, skinData);
} else if(type.equals("Cluster")) {
ClusterData clusterData = new ClusterData();
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("Indexes"))
clusterData.indexes = (int[]) e.properties.get(0);
else if(e.id.equals("Weights"))
@ -497,7 +497,7 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadAnimLayer(FBXElement element) {
private void loadAnimLayer(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
@ -508,12 +508,12 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadAnimCurve(FBXElement element) {
private void loadAnimCurve(FbxElement element) {
long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2);
if(type.equals("")) {
AnimCurveData data = new AnimCurveData();
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("KeyTime"))
data.keyTimes = (long[]) e.properties.get(0);
else if(e.id.equals("KeyValueFloat"))
@ -523,15 +523,15 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadAnimNode(FBXElement element) {
private void loadAnimNode(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("")) {
Double x = null, y = null, z = null;
for(FBXElement e : element.children) {
for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) {
for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("d|X"))
@ -554,8 +554,8 @@ public class SceneLoader implements AssetLoader {
}
}
private void loadConnections(FBXElement element) {
for(FBXElement e : element.children) {
private void loadConnections(FbxElement element) {
for(FbxElement e : element.children) {
if(e.id.equals("C")) {
String type = (String) e.properties.get(0);
long objId, refId;

@ -0,0 +1,144 @@
/*
* 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.asset.AssetManager;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
public class FbxAnimCurve extends FbxObject {
private long[] keyTimes;
private float[] keyValues;
public FbxAnimCurve(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement e : element.children) {
if (e.id.equals("KeyTime")) {
keyTimes = (long[]) e.properties.get(0);
} else if (e.id.equals("KeyValueFloat")) {
keyValues = (float[]) e.properties.get(0);
}
}
long time = -1;
for (int i = 0; i < keyTimes.length; i++) {
if (time >= keyTimes[i]) {
throw new UnsupportedOperationException("Keyframe times must be sequential, but they are not.");
}
time = keyTimes[i];
}
}
/**
* Get the times for the keyframes.
* @return Keyframe times.
*/
public long[] getKeyTimes() {
return keyTimes;
}
/**
* Retrieve the curve value at the given time.
* If the curve has no data, 0 is returned.
* If the time is outside the curve, then the closest value is returned.
* If the time isn't on an exact keyframe, linear interpolation is used
* to determine the value between the keyframes at the given time.
* @param time The time to get the curve value at (in FBX time units).
* @return The value at the given time.
*/
public float getValueAtTime(long time) {
if (keyTimes.length == 0) {
return 0;
}
// If the time is outside the range,
// we just return the closest value. (No extrapolation)
if (time <= keyTimes[0]) {
return keyValues[0];
} else if (time >= keyTimes[keyTimes.length - 1]) {
return keyValues[keyValues.length - 1];
}
int startFrame = 0;
int endFrame = 1;
int lastFrame = keyTimes.length - 1;
for (int i = 0; i < lastFrame && keyTimes[i] < time; ++i) {
startFrame = i;
endFrame = i + 1;
}
long keyTime1 = keyTimes[startFrame];
float keyValue1 = keyValues[startFrame];
long keyTime2 = keyTimes[endFrame];
float keyValue2 = keyValues[endFrame];
if (keyTime2 == time) {
return keyValue2;
}
long prevToNextDelta = keyTime2 - keyTime1;
long prevToCurrentDelta = time - keyTime1;
float lerpAmount = (float)prevToCurrentDelta / prevToNextDelta;
return FastMath.interpolateLinear(lerpAmount, keyValue1, keyValue2);
}
@Override
protected Object toJmeObject() {
// An AnimCurve has no jME3 representation.
// The parent AnimCurveNode is responsible to create the jME3
// representation.
throw new UnsupportedOperationException("No jME3 object conversion available");
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,147 @@
/*
* 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.asset.AssetManager;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxAnimCurveNode extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxAnimCurveNode.class.getName());
private final Map<FbxNode, String> influencedNodePropertiesMap = new HashMap<FbxNode, String>();
private final Map<String, FbxAnimCurve> propertyToCurveMap = new HashMap<String, FbxAnimCurve>();
private final Map<String, Float> propertyToDefaultMap = new HashMap<String, Float>();
public FbxAnimCurveNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement prop : element.getFbxProperties()) {
String propName = (String) prop.properties.get(0);
String propType = (String) prop.properties.get(1);
if (propType.equals("Number")) {
float propValue = ((Double) prop.properties.get(4)).floatValue();
propertyToDefaultMap.put(propName, propValue);
}
}
}
public void addInfluencedNode(FbxNode node, String property) {
influencedNodePropertiesMap.put(node, property);
}
public Map<FbxNode, String> getInfluencedNodeProperties() {
return influencedNodePropertiesMap;
}
public Collection<FbxAnimCurve> getCurves() {
return propertyToCurveMap.values();
}
public Vector3f getVector3Value(long time) {
Vector3f value = new Vector3f();
FbxAnimCurve xCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X);
FbxAnimCurve yCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y);
FbxAnimCurve zCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z);
Float xDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X);
Float yDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y);
Float zDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z);
value.x = xCurve != null ? xCurve.getValueAtTime(time) : xDefault;
value.y = yCurve != null ? yCurve.getValueAtTime(time) : yDefault;
value.z = zCurve != null ? zCurve.getValueAtTime(time) : zDefault;
return value;
}
/**
* Converts the euler angles from {@link #getVector3Value(long)} to
* a quaternion rotation.
* @param time Time at which to get the euler angles.
* @return The rotation at time
*/
public Quaternion getQuaternionValue(long time) {
Vector3f eulerAngles = getVector3Value(time);
System.out.println("\tT: " + time + ". Rotation: " +
eulerAngles.x + ", " +
eulerAngles.y + ", " + eulerAngles.z);
Quaternion q = new Quaternion();
q.fromAngles(eulerAngles.x * FastMath.DEG_TO_RAD,
eulerAngles.y * FastMath.DEG_TO_RAD,
eulerAngles.z * FastMath.DEG_TO_RAD);
return q;
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
if (!(object instanceof FbxAnimCurve)) {
unsupportedConnectObjectProperty(object, property);
}
if (!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_X) &&
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Y) &&
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Z) &&
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_VISIBILITY)) {
logger.log(Level.WARNING, "Animating the dimension ''{0}'' is not "
+ "supported yet. Ignoring.", property);
return;
}
if (propertyToCurveMap.containsKey(property)) {
throw new UnsupportedOperationException("!");
}
propertyToCurveMap.put(property, (FbxAnimCurve) object);
}
}

@ -0,0 +1,82 @@
/*
* 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.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
public class FbxAnimLayer extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxAnimLayer.class.getName());
private final List<FbxAnimCurveNode> animCurves = new ArrayList<FbxAnimCurveNode>();
public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
// No known properties for layers..
// Also jME3 doesn't support multiple layers anyway.
}
public List<FbxAnimCurveNode> getAnimationCurveNodes() {
return Collections.unmodifiableList(animCurves);
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (!(object instanceof FbxAnimCurveNode)) {
unsupportedConnectObject(object);
}
animCurves.add((FbxAnimCurveNode) object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,111 @@
/*
* 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.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxAnimStack extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxAnimStack.class.getName());
private float duration;
private FbxAnimLayer layer0;
public FbxAnimStack(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement child : element.getFbxProperties()) {
String propName = (String) child.properties.get(0);
if (propName.equals("LocalStop")) {
long durationLong = (Long)child.properties.get(4);
duration = (float) (durationLong * FbxAnimUtil.SECONDS_PER_UNIT);
}
}
}
// /**
// * Finds out which FBX nodes this animation is going to influence.
// *
// * @return A list of FBX nodes that the stack's curves are influencing.
// */
// public Set<FbxNode> getInfluencedNodes() {
// HashSet<FbxNode> influencedNodes = new HashSet<FbxNode>();
// if (layer0 == null) {
// return influencedNodes;
// }
// for (FbxAnimCurveNode curveNode : layer0.getAnimationCurveNodes()) {
// influencedNodes.addAll(curveNode.getInfluencedNodes());
// }
// return influencedNodes;
// }
public float getDuration() {
return duration;
}
public FbxAnimLayer[] getLayers() {
return new FbxAnimLayer[]{ layer0 };
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (!(object instanceof FbxAnimLayer)) {
unsupportedConnectObject(object);
}
if (layer0 != null) {
logger.log(Level.WARNING, "jME3 does not support layered animation. "
+ "Only first layer has been loaded.");
return;
}
layer0 = (FbxAnimLayer) object;
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,44 @@
/*
* 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;
public class FbxAnimUtil {
/**
* Conversion factor from FBX animation time unit to seconds.
*/
public static final double SECONDS_PER_UNIT = 1 / 46186158000d;
public static final String CURVE_NODE_PROPERTY_X = "d|X";
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";
}

@ -0,0 +1,103 @@
/*
* 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.asset.AssetManager;
import com.jme3.math.Matrix4f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FbxId;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.HashMap;
import java.util.Map;
public class FbxBindPose extends FbxObject<Map<FbxId, Matrix4f>> {
private final Map<FbxId, Matrix4f> bindPose = new HashMap<FbxId, Matrix4f>();
public FbxBindPose(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement child : element.children) {
if (!child.id.equals("PoseNode")) {
continue;
}
FbxId node = null;
float[] 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);
if (matDataDoubles.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];
}
}
}
if (node != null && matData != null) {
Matrix4f matrix = new Matrix4f(matData);
bindPose.put(node, matrix);
}
}
}
@Override
protected Map<FbxId, Matrix4f> toJmeObject() {
return bindPose;
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -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.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxCluster extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxCluster.class.getName());
private int[] indexes;
private double[] weights;
private FbxLimbNode limb;
public FbxCluster(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement e : element.children) {
if (e.id.equals("Indexes")) {
indexes = (int[]) e.properties.get(0);
} else if (e.id.equals("Weights")) {
weights = (double[]) e.properties.get(0);
}
}
}
public int[] getVertexIndices() {
return indexes;
}
public double[] getWeights() {
return weights;
}
public FbxLimbNode getLimb() {
return limb;
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxLimbNode) {
if (limb != null) {
logger.log(Level.WARNING, "This cluster already has a limb attached. Ignoring.");
return;
}
limb = (FbxLimbNode) object;
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,94 @@
/*
* 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.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;
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 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;
}
}

@ -0,0 +1,66 @@
/*
* 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.asset.AssetManager;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.ArrayList;
import java.util.List;
public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> {
private final List<FbxCluster> clusters = new ArrayList<FbxCluster>();
public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
protected List<FbxCluster> toJmeObject() {
return clusters;
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxCluster) {
clusters.add((FbxCluster) object);
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,202 @@
/*
* 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.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Maps animation stacks to influenced nodes.
* Will be used later to create jME3 tracks.
*/
public final class FbxToJmeTrack {
public FbxAnimStack animStack;
public FbxAnimLayer animLayer;
public FbxNode node;
// These are not used in map lookups.
public transient final Map<String, FbxAnimCurveNode> animCurves = new HashMap<String, FbxAnimCurveNode>();
public long[] getKeyTimes() {
Set<Long> keyFrameTimesSet = new HashSet<Long>();
for (FbxAnimCurveNode curveNode : animCurves.values()) {
for (FbxAnimCurve curve : curveNode.getCurves()) {
for (long keyTime : curve.getKeyTimes()) {
keyFrameTimesSet.add(keyTime);
}
}
}
long[] keyFrameTimes = new long[keyFrameTimesSet.size()];
int i = 0;
for (Long keyFrameTime : keyFrameTimesSet) {
keyFrameTimes[i++] = keyFrameTime;
}
Arrays.sort(keyFrameTimes);
return keyFrameTimes;
}
/**
* Generate a {@link BoneTrack} from the animation data, for the given
* boneIndex.
*
* @param boneIndex The bone index for which track data is generated for.
* @param inverseBindPose Inverse bind pose of the bone (in world space).
* @return A BoneTrack containing the animation data, for the specified
* boneIndex.
*/
public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) {
return (BoneTrack) toJmeTrackInternal(boneIndex, inverseBindPose);
}
public SpatialTrack toJmeSpatialTrack() {
return (SpatialTrack) toJmeTrackInternal(-1, null);
}
public float getDuration() {
long[] keyframes = getKeyTimes();
return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT);
}
private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) {
Transform t = new Transform();
t.setTranslation(translation);
t.setRotation(rotation);
if (scale != null) {
t.setScale(scale);
}
t.combineWithParent(inverseBindPose);
t.getTranslation(translation);
t.getRotation(rotation);
if (scale != null) {
t.getScale(scale);
}
}
private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) {
float duration = animStack.getDuration();
FbxAnimCurveNode translationCurve = animCurves.get("Lcl Translation");
FbxAnimCurveNode rotationCurve = animCurves.get("Lcl Rotation");
FbxAnimCurveNode scalingCurve = animCurves.get("Lcl Scaling");
long[] fbxTimes = getKeyTimes();
float[] times = new float[fbxTimes.length];
// Translations / Rotations must be set on all tracks.
// (Required for jME3)
Vector3f[] translations = new Vector3f[fbxTimes.length];
Quaternion[] rotations = new Quaternion[fbxTimes.length];
Vector3f[] scales = null;
if (scalingCurve != null) {
scales = new Vector3f[fbxTimes.length];
}
for (int i = 0; i < fbxTimes.length; i++) {
long fbxTime = fbxTimes[i];
float time = (float) (fbxTime * FbxAnimUtil.SECONDS_PER_UNIT);
if (time > duration) {
// Expand animation duration to fit the curve.
duration = time;
System.out.println("actual duration: " + duration);
}
times[i] = time;
if (translationCurve != null) {
translations[i] = translationCurve.getVector3Value(fbxTime);
} else {
translations[i] = new Vector3f();
}
if (rotationCurve != null) {
rotations[i] = rotationCurve.getQuaternionValue(fbxTime);
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();
}
}
} else {
rotations[i] = new Quaternion();
}
if (scalingCurve != null) {
scales[i] = scalingCurve.getVector3Value(fbxTime);
}
if (inverseBindPose != null) {
applyInverse(translations[i], rotations[i], scales != null ? scales[i] : null, inverseBindPose);
}
}
if (boneIndex == -1) {
return new SpatialTrack(times, translations, rotations, scales);
} else {
if (scales != null) {
return new BoneTrack(boneIndex, times, translations, rotations, scales);
} else {
return new BoneTrack(boneIndex, times, translations, rotations);
}
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 79 * hash + this.animStack.hashCode();
hash = 79 * hash + this.animLayer.hashCode();
hash = 79 * hash + this.node.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
final FbxToJmeTrack other = (FbxToJmeTrack) obj;
return this.node == other.node
&& this.animStack == other.animStack
&& this.animLayer == other.animLayer;
}
}

@ -37,7 +37,8 @@ import java.lang.reflect.Array;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import static org.omg.IOP.IORHelper.id;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Quick n' dirty dumper of FBX binary files.
@ -46,12 +47,14 @@ import static org.omg.IOP.IORHelper.id;
*
* @author Kirill Vainer
*/
public final class FBXDump {
public final class FbxDump {
private static final Logger logger = Logger.getLogger(FbxDump.class.getName());
private static final DecimalFormat DECIMAL_FORMAT
= new DecimalFormat("0.0000000000");
private FBXDump() { }
private FbxDump() { }
/**
* Creates a map between object UIDs and the objects themselves.
@ -59,16 +62,17 @@ public final class FBXDump {
* @param file The file to create the mappings for.
* @return The UID to object map.
*/
private static Map<Long, FBXElement> createUidToObjectMap(FBXFile file) {
Map<Long, FBXElement> uidToObjectMap = new HashMap<Long, FBXElement>();
for (FBXElement rootElement : file.rootElements) {
private static Map<FbxId, FbxElement> createUidToObjectMap(FbxFile file) {
Map<FbxId, FbxElement> uidToObjectMap = new HashMap<FbxId, FbxElement>();
for (FbxElement rootElement : file.rootElements) {
if (rootElement.id.equals("Objects")) {
for (FBXElement fbxObj : rootElement.children) {
if (fbxObj.propertiesTypes[0] != 'L') {
continue; // error
for (FbxElement fbxObj : rootElement.children) {
FbxId uid = FbxId.getObjectId(fbxObj);
if (uid != null) {
uidToObjectMap.put(uid, fbxObj);
} else {
logger.log(Level.WARNING, "Cannot determine ID for object: {0}", fbxObj);
}
Long uid = (Long) fbxObj.properties.get(0);
uidToObjectMap.put(uid, fbxObj);
}
}
}
@ -80,8 +84,8 @@ public final class FBXDump {
*
* @param file the file to dump.
*/
public static void dumpFBX(FBXFile file) {
dumpFBX(file, System.out);
public static void dumpFile(FbxFile file) {
dumpFile(file, System.out);
}
/**
@ -90,11 +94,11 @@ public final class FBXDump {
* @param file the file to dump.
* @param out the output stream where to output.
*/
public static void dumpFBX(FBXFile file, OutputStream out) {
Map<Long, FBXElement> uidToObjectMap = createUidToObjectMap(file);
public static void dumpFile(FbxFile file, OutputStream out) {
Map<FbxId, FbxElement> uidToObjectMap = createUidToObjectMap(file);
PrintStream ps = new PrintStream(out);
for (FBXElement rootElement : file.rootElements) {
dumpFBXElement(rootElement, ps, 0, uidToObjectMap);
for (FbxElement rootElement : file.rootElements) {
dumpElement(rootElement, ps, 0, uidToObjectMap);
}
}
@ -113,9 +117,9 @@ public final class FBXDump {
return string.replaceAll("\u0000\u0001", "::");
}
protected static void dumpFBXProperty(String id, char propertyType,
protected static void dumpProperty(String id, char propertyType,
Object property, PrintStream ps,
Map<Long, FBXElement> uidToObjectMap) {
Map<FbxId, FbxElement> uidToObjectMap) {
switch (propertyType) {
case 'S':
// String
@ -125,13 +129,19 @@ public final class FBXDump {
case 'R':
// RAW data.
byte[] bytes = (byte[]) property;
ps.print("[");
for (int j = 0; j < bytes.length; j++) {
int numToPrint = Math.min(10 * 1024, bytes.length);
ps.print("(size = ");
ps.print(bytes.length);
ps.print(") [");
for (int j = 0; j < numToPrint; j++) {
ps.print(String.format("%02X", bytes[j] & 0xff));
if (j != bytes.length - 1) {
ps.print(" ");
}
}
if (numToPrint < bytes.length) {
ps.print(" ...");
}
ps.print("]");
break;
case 'D':
@ -159,7 +169,7 @@ public final class FBXDump {
// If this is a connection, decode UID into object name.
if (id.equals("C")) {
Long uid = (Long) property;
FBXElement element = uidToObjectMap.get(uid);
FbxElement element = uidToObjectMap.get(FbxId.create(uid));
if (element != null) {
String name = (String) element.properties.get(1);
ps.print("\"" + convertFBXString(name) + "\"");
@ -178,7 +188,7 @@ public final class FBXDump {
int length = Array.getLength(property);
for (int j = 0; j < length; j++) {
Object arrayEntry = Array.get(property, j);
dumpFBXProperty(id, Character.toUpperCase(propertyType), arrayEntry, ps, uidToObjectMap);
dumpProperty(id, Character.toUpperCase(propertyType), arrayEntry, ps, uidToObjectMap);
if (j != length - 1) {
ps.print(",");
}
@ -189,24 +199,24 @@ public final class FBXDump {
}
}
protected static void dumpFBXElement(FBXElement el, PrintStream ps,
int indent, Map<Long, FBXElement> uidToObjectMap) {
protected static void dumpElement(FbxElement el, PrintStream ps,
int indent, Map<FbxId, FbxElement> uidToObjectMap) {
// 4 spaces per tab should be OK.
String indentStr = indent(indent * 4);
String textId = el.id;
// Properties are called 'P' and connections are called 'C'.
if (el.id.equals("P")) {
textId = "Property";
} else if (el.id.equals("C")) {
textId = "Connect";
}
// if (el.id.equals("P")) {
// textId = "Property";
// } else if (el.id.equals("C")) {
// textId = "Connect";
// }
ps.print(indentStr + textId + ": ");
for (int i = 0; i < el.properties.size(); i++) {
Object property = el.properties.get(i);
char propertyType = el.propertiesTypes[i];
dumpFBXProperty(el.id, propertyType, property, ps, uidToObjectMap);
dumpProperty(el.id, propertyType, property, ps, uidToObjectMap);
if (i != el.properties.size() - 1) {
ps.print(", ");
}
@ -215,8 +225,8 @@ public final class FBXDump {
ps.println();
} else {
ps.println(" {");
for (FBXElement childElement : el.children) {
dumpFBXElement(childElement, ps, indent + 1, uidToObjectMap);
for (FbxElement childElement : el.children) {
dumpElement(childElement, ps, indent + 1, uidToObjectMap);
}
ps.println(indentStr + "}");
}

@ -0,0 +1,124 @@
/*
* Copyright (c) 2009-2014 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.file;
import java.util.ArrayList;
import java.util.List;
public class FbxElement {
public String id;
public List<Object> properties;
/*
* Y - signed short
* C - boolean
* I - signed integer
* F - float
* D - double
* L - long
* R - byte array
* S - string
* f - array of floats
* i - array of ints
* d - array of doubles
* l - array of longs
* b - array of boleans
* c - array of unsigned bytes (represented as array of ints)
*/
public char[] propertiesTypes;
public List<FbxElement> children = new ArrayList<FbxElement>();
public FbxElement(int propsCount) {
this.properties = new ArrayList<Object>(propsCount);
this.propertiesTypes = new char[propsCount];
}
public FbxElement getChildById(String name) {
for (FbxElement element : children) {
if (element.id.equals(name)) {
return element;
}
}
return null;
}
public List<FbxElement> getFbxProperties() {
List<FbxElement> props = new ArrayList<FbxElement>();
FbxElement propsElement = null;
boolean legacy = false;
for (FbxElement element : children) {
if (element.id.equals("Properties70")) {
propsElement = element;
break;
} else if (element.id.equals("Properties60")) {
legacy = true;
propsElement = element;
break;
}
}
if (propsElement == null) {
return props;
}
for (FbxElement prop : propsElement.children) {
if (prop.id.equals("P") || prop.id.equals("Property")) {
if (legacy) {
char[] types = new char[prop.propertiesTypes.length + 1];
types[0] = prop.propertiesTypes[0];
types[1] = prop.propertiesTypes[0];
System.arraycopy(prop.propertiesTypes, 1, types, 2, types.length - 2);
List<Object> values = new ArrayList<Object>(prop.properties);
values.add(1, values.get(0));
FbxElement dummyProp = new FbxElement(types.length);
dummyProp.children = prop.children;
dummyProp.id = prop.id;
dummyProp.propertiesTypes = types;
dummyProp.properties = values;
props.add(dummyProp);
} else {
props.add(prop);
}
}
}
return props;
}
@Override
public String toString() {
return "FBXElement[id=" + id + ", numProps=" + properties.size() + ", numChildren=" + children.size() + "]";
}
}

@ -34,9 +34,13 @@ package com.jme3.scene.plugins.fbx.file;
import java.util.ArrayList;
import java.util.List;
public class FBXFile {
public class FbxFile {
public List<FBXElement> rootElements = new ArrayList<FBXElement>();
public List<FbxElement> rootElements = new ArrayList<FbxElement>();
public long version;
@Override
public String toString() {
return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]";
}
}

@ -0,0 +1,130 @@
/*
* 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.file;
public abstract class FbxId {
public static final FbxId ROOT = new LongFbxId(0);
protected FbxId() { }
private static final class StringFbxId extends FbxId {
private final String id;
public StringFbxId(String id) {
this.id = id;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != StringFbxId.class) {
return false;
}
return this.id.equals(((StringFbxId) obj).id);
}
@Override
public String toString() {
return id;
}
@Override
public boolean isNull() {
return id.equals("Scene\u0000\u0001Model");
}
}
private static final class LongFbxId extends FbxId {
private final long id;
public LongFbxId(long id) {
this.id = id;
}
@Override
public int hashCode() {
return (int) (this.id ^ (this.id >>> 32));
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != LongFbxId.class) {
return false;
}
return this.id == ((LongFbxId) obj).id;
}
@Override
public boolean isNull() {
return id == 0;
}
@Override
public String toString() {
return Long.toString(id);
}
}
public abstract boolean isNull();
public static FbxId create(Object obj) {
if (obj instanceof Long) {
return new LongFbxId((Long)obj);
} else if (obj instanceof String) {
return new StringFbxId((String)obj);
} else {
throw new UnsupportedOperationException("Unsupported ID object type: " + obj.getClass());
}
}
public static FbxId getObjectId(FbxElement el) {
if (el.propertiesTypes.length == 2
&& el.propertiesTypes[0] == 'S'
&& el.propertiesTypes[1] == 'S') {
return new StringFbxId((String) el.properties.get(0));
} else if (el.propertiesTypes.length == 3
&& el.propertiesTypes[0] == 'L'
&& el.propertiesTypes[1] == 'S'
&& el.propertiesTypes[2] == 'S') {
return new LongFbxId((Long) el.properties.get(0));
} else {
return null;
}
}
}

@ -40,8 +40,8 @@ import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.zip.InflaterInputStream;
public class FBXReader {
public class FbxReader {
public static final int BLOCK_SENTINEL_LENGTH = 13;
public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH];
/**
@ -49,9 +49,9 @@ public class FBXReader {
* "Kaydara FBX Binary\x20\x20\x00\x1a\x00"
*/
public static final byte[] HEAD_MAGIC = new byte[]{0x4b, 0x61, 0x79, 0x64, 0x61, 0x72, 0x61, 0x20, 0x46, 0x42, 0x58, 0x20, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x20, 0x00, 0x1a, 0x00};
public static FBXFile readFBX(InputStream stream) throws IOException {
FBXFile fbxFile = new FBXFile();
public static FbxFile readFBX(InputStream stream) throws IOException {
FbxFile fbxFile = new FbxFile();
// Read file to byte buffer so we can know current position in file
ByteBuffer byteBuffer = readToByteBuffer(stream);
try {
@ -61,27 +61,29 @@ public class FBXReader {
// Check majic header
byte[] majic = getBytes(byteBuffer, HEAD_MAGIC.length);
if(!Arrays.equals(HEAD_MAGIC, majic))
throw new IOException("Not FBX file");
throw new IOException("Either ASCII FBX or corrupt file. "
+ "Only binary FBX files are supported");
// Read version
fbxFile.version = getUInt(byteBuffer);
// Read root elements
while(true) {
FBXElement e = readFBXElement(byteBuffer);
FbxElement e = readFBXElement(byteBuffer);
if(e == null)
break;
fbxFile.rootElements.add(e);
}
return fbxFile;
}
private static FBXElement readFBXElement(ByteBuffer byteBuffer) throws IOException {
private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException {
long endOffset = getUInt(byteBuffer);
if(endOffset == 0)
return null;
long propCount = getUInt(byteBuffer);
getUInt(byteBuffer); // Properties length unused
FBXElement element = new FBXElement((int) propCount);
FbxElement element = new FbxElement((int) propCount);
element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer)));
for(int i = 0; i < propCount; ++i) {

@ -0,0 +1,190 @@
/*
* 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.material;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetManager;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.asset.TextureKey;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Image;
import com.jme3.util.PlaceholderAssets;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class FbxImage extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxImage.class.getName());
protected TextureKey key;
protected String type; // = "Clip"
protected String filePath; // = "C:\Whatever\Blah\Texture.png"
protected String relativeFilePath; // = "..\Blah\Texture.png"
protected byte[] content; // = null, byte[0] OR embedded image data (unknown format?)
public FbxImage(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
if (element.propertiesTypes.length == 3) {
type = (String) element.properties.get(2);
} else {
type = (String) element.properties.get(1);
}
if (type.equals("Clip")) {
for (FbxElement e : element.children) {
if (e.id.equals("Type")) {
type = (String) e.properties.get(0);
} else if (e.id.equals("FileName")) {
filePath = (String) e.properties.get(0);
} else if (e.id.equals("RelativeFilename")) {
relativeFilePath = (String) e.properties.get(0);
} else if (e.id.equals("Content")) {
if (e.properties.size() > 0) {
byte[] storedContent = (byte[]) e.properties.get(0);
if (storedContent.length > 0) {
this.content = storedContent;
}
}
}
}
}
}
private Image loadImageSafe(AssetManager assetManager, TextureKey texKey) {
try {
return assetManager.loadTexture(texKey).getImage();
} catch (AssetNotFoundException ex) {
return null;
} catch (AssetLoadException ex) {
logger.log(Level.WARNING, "Error when loading image: " + texKey, ex);
return null;
}
}
private static String getFileName(String filePath) {
// NOTE: Gotta do it this way because new File().getParent()
// will not strip forward slashes on Linux / Mac OS X.
int fwdSlashIdx = filePath.lastIndexOf("\\");
int bkSlashIdx = filePath.lastIndexOf("/");
if (fwdSlashIdx != -1) {
filePath = filePath.substring(fwdSlashIdx + 1);
} else if (bkSlashIdx != -1) {
filePath = filePath.substring(bkSlashIdx + 1);
}
return filePath;
}
/**
* The texture key that was used to load the image.
* Only valid after {@link #getJmeObject()} has been called.
* @return the key that was used to load the image.
*/
public TextureKey getTextureKey() {
return key;
}
@Override
protected Object toJmeObject() {
Image image = null;
String fileName = null;
String relativeFilePathJme;
if (filePath != null) {
fileName = getFileName(filePath);
} else if (relativeFilePath != null) {
fileName = getFileName(relativeFilePath);
}
if (fileName != null) {
try {
// Try to load filename relative to FBX folder
key = new TextureKey(sceneFolderName + fileName);
key.setGenerateMips(true);
image = loadImageSafe(assetManager, key);
// Try to load relative filepath relative to FBX folder
if (image == null && relativeFilePath != null) {
// Convert Windows paths to jME3 paths
relativeFilePathJme = relativeFilePath.replace('\\', '/');
key = new TextureKey(sceneFolderName + relativeFilePathJme);
key.setGenerateMips(true);
image = loadImageSafe(assetManager, key);
}
// Try to load embedded image
if (image == null && content != null && content.length > 0) {
key = new TextureKey(fileName);
key.setGenerateMips(true);
InputStream is = new ByteArrayInputStream(content);
image = assetManager.loadAssetFromStream(key, is).getImage();
// NOTE: embedded texture doesn't exist in the asset manager,
// so the texture key must not be saved.
key = null;
}
} catch (AssetLoadException ex) {
logger.log(Level.WARNING, "Error while attempting to load texture {0}:\n{1}",
new Object[]{name, ex.toString()});
}
}
if (image == null) {
logger.log(Level.WARNING, "Cannot locate {0} for texture {1}", new Object[]{fileName, name});
image = PlaceholderAssets.getPlaceholderImage(assetManager);
}
// NOTE: At this point, key will be set to the last
// attempted texture key that we attempted to load.
return image;
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,363 @@
/*
* 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.material;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Texture;
import com.jme3.texture.image.ColorSpace;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxMaterial extends FbxObject<Material> {
private static final Logger logger = Logger.getLogger(FbxMaterial.class.getName());
private String shadingModel; // TODO: do we care about this? lambert just has no specular?
private final FbxMaterialProperties properties = new FbxMaterialProperties();
public FbxMaterial(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
if(!getSubclassName().equals("")) {
return;
}
FbxElement shadingModelEl = element.getChildById("ShadingModel");
if (shadingModelEl != null) {
shadingModel = (String) shadingModelEl.properties.get(0);
if (!shadingModel.equals("")) {
if (!shadingModel.equalsIgnoreCase("phong") &&
!shadingModel.equalsIgnoreCase("lambert")) {
logger.log(Level.WARNING, "FBX material uses unknown shading model: {0}. "
+ "Material may display incorrectly.", shadingModel);
}
}
}
for (FbxElement child : element.getFbxProperties()) {
properties.setPropertyFromElement(child);
}
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
if (!(object instanceof FbxTexture)) {
unsupportedConnectObjectProperty(object, property);
}
properties.setPropertyTexture(property, (FbxTexture) object);
}
private static void multRGB(ColorRGBA color, float factor) {
color.r *= factor;
color.g *= factor;
color.b *= factor;
}
@Override
protected Material toJmeObject() {
ColorRGBA ambient = null;
ColorRGBA diffuse = null;
ColorRGBA specular = null;
ColorRGBA transp = null;
ColorRGBA emissive = null;
float shininess = 1f;
boolean separateTexCoord = false;
Texture diffuseMap = null;
Texture specularMap = null;
Texture normalMap = null;
Texture transpMap = null;
Texture emitMap = null;
Texture aoMap = null;
FbxTexture fbxDiffuseMap = null;
Object diffuseColor = properties.getProperty("DiffuseColor");
if (diffuseColor != null) {
if (diffuseColor instanceof ColorRGBA) {
diffuse = ((ColorRGBA) diffuseColor).clone();
} else if (diffuseColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) diffuseColor;
fbxDiffuseMap = tex;
diffuseMap = tex.getJmeObject();
diffuseMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object diffuseFactor = properties.getProperty("DiffuseFactor");
if (diffuseFactor != null && diffuseFactor instanceof Float) {
float factor = (Float)diffuseFactor;
if (diffuse != null) {
multRGB(diffuse, factor);
} else {
diffuse = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object specularColor = properties.getProperty("SpecularColor");
if (specularColor != null) {
if (specularColor instanceof ColorRGBA) {
specular = ((ColorRGBA) specularColor).clone();
} else if (specularColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) specularColor;
specularMap = tex.getJmeObject();
specularMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object specularFactor = properties.getProperty("SpecularFactor");
if (specularFactor != null && specularFactor instanceof Float) {
float factor = (Float)specularFactor;
if (specular != null) {
multRGB(specular, factor);
} else {
specular = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object transparentColor = properties.getProperty("TransparentColor");
if (transparentColor != null) {
if (transparentColor instanceof ColorRGBA) {
transp = ((ColorRGBA) transparentColor).clone();
} else if (transparentColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) transparentColor;
transpMap = tex.getJmeObject();
transpMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object transparencyFactor = properties.getProperty("TransparencyFactor");
if (transparencyFactor != null && transparencyFactor instanceof Float) {
float factor = (Float)transparencyFactor;
if (transp != null) {
transp.a *= factor;
} else {
transp = new ColorRGBA(1f, 1f, 1f, factor);
}
}
Object emissiveColor = properties.getProperty("EmissiveColor");
if (emissiveColor != null) {
if (emissiveColor instanceof ColorRGBA) {
emissive = ((ColorRGBA)emissiveColor).clone();
} else if (emissiveColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) emissiveColor;
emitMap = tex.getJmeObject();
emitMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object emissiveFactor = properties.getProperty("EmissiveFactor");
if (emissiveFactor != null && emissiveFactor instanceof Float) {
float factor = (Float)emissiveFactor;
if (emissive != null) {
multRGB(emissive, factor);
} else {
emissive = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object ambientColor = properties.getProperty("AmbientColor");
if (ambientColor != null && ambientColor instanceof ColorRGBA) {
ambient = ((ColorRGBA)ambientColor).clone();
}
Object ambientFactor = properties.getProperty("AmbientFactor");
if (ambientFactor != null && ambientFactor instanceof Float) {
float factor = (Float)ambientFactor;
if (ambient != null) {
multRGB(ambient, factor);
} else {
ambient = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object shininessFactor = properties.getProperty("Shininess");
if (shininessFactor != null) {
if (shininessFactor instanceof Float) {
shininess = (Float) shininessFactor;
} else if (shininessFactor instanceof FbxTexture) {
// TODO: support shininess textures
}
}
Object bumpNormal = properties.getProperty("NormalMap");
if (bumpNormal != null) {
if (bumpNormal instanceof FbxTexture) {
// TODO: check all meshes that use this material have tangents
// otherwise shading errors occur
FbxTexture tex = (FbxTexture) bumpNormal;
normalMap = tex.getJmeObject();
normalMap.getImage().setColorSpace(ColorSpace.Linear);
}
}
Object aoColor = properties.getProperty("DiffuseColor2");
if (aoColor != null) {
if (aoColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) aoColor;
if (tex.getUvSet() != null && fbxDiffuseMap != null) {
if (!tex.getUvSet().equals(fbxDiffuseMap.getUvSet())) {
separateTexCoord = true;
}
}
aoMap = tex.getJmeObject();
aoMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
// TODO: how to disable transparency from diffuse map?? Need "UseAlpha" again..
assert ambient == null || ambient.a == 1f;
assert diffuse == null || diffuse.a == 1f;
assert specular == null || specular.a == 1f;
assert emissive == null || emissive.a == 1f;
assert transp == null || (transp.r == 1f && transp.g == 1f && transp.b == 1f);
// If shininess is less than 1.0, the lighting shader won't be able
// to handle it. Gotta disable specularity then.
if (shininess < 1f) {
shininess = 1f;
specular = ColorRGBA.Black;
}
// Try to guess if we need to enable alpha blending.
// FBX does not specify this explicitly.
boolean useAlphaBlend = false;
if (diffuseMap != null && diffuseMap == transpMap) {
// jME3 already uses alpha from diffuseMap
// (if alpha blend is enabled)
useAlphaBlend = true;
transpMap = null;
} else if (diffuseMap != null && transpMap != null && diffuseMap != transpMap) {
// TODO: potential bug here. Alpha from diffuse may
// leak unintentionally.
useAlphaBlend = true;
} else if (transpMap != null) {
// We have alpha map but no diffuse map, OK.
useAlphaBlend = true;
}
if (transp != null && transp.a != 1f) {
// Consolidate transp into diffuse
// (jME3 doesn't use a separate alpha color)
// TODO: potential bug here. Alpha from diffuse may
// leak unintentionally.
useAlphaBlend = true;
if (diffuse != null) {
diffuse.a = transp.a;
} else {
diffuse = transp;
}
}
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setName(name);
// TODO: load this from FBX material.
mat.setReceivesShadows(true);
if (useAlphaBlend) {
// No idea if this is a transparent or translucent model, gotta guess..
mat.setTransparent(true);
mat.setFloat("AlphaDiscardThreshold", 0.01f);
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
}
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
// Set colors.
if (ambient != null || diffuse != null || specular != null) {
// If either of those is set, we have to set them all.
// NOTE: default specular is black, unless it is set explicitly.
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Ambient", /*ambient != null ? ambient :*/ ColorRGBA.White);
mat.setColor("Diffuse", diffuse != null ? diffuse : ColorRGBA.White);
mat.setColor("Specular", specular != null ? specular : ColorRGBA.Black);
}
if (emissive != null) {
mat.setColor("GlowColor", emissive);
}
// Set shininess.
if (shininess > 1f) {
// Convert shininess from
// Phong (FBX shading model) to Blinn (jME3 shading model).
float blinnShininess = (shininess * 5.1f) + 1f;
mat.setFloat("Shininess", blinnShininess);
}
// Set textures.
if (diffuseMap != null) {
mat.setTexture("DiffuseMap", diffuseMap);
}
if (specularMap != null) {
mat.setTexture("SpecularMap", specularMap);
}
if (normalMap != null) {
mat.setTexture("NormalMap", normalMap);
}
if (transpMap != null) {
// mat.setTexture("AlphaMap", transpMap);
}
if (emitMap != null) {
mat.setTexture("GlowMap", emitMap);
}
if (aoMap != null) {
mat.setTexture("LightMap", aoMap);
if (separateTexCoord) {
mat.setBoolean("SeparateTexCoord", true);
}
}
return mat;
}
}

@ -0,0 +1,234 @@
/*
* 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.material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxMaterialProperties {
private static final Logger logger = Logger.getLogger(FbxMaterialProperties.class.getName());
private static final Map<String, FBXMaterialProperty> propertyMetaMap = new HashMap<String, FBXMaterialProperty>();
private final Map<String, Object> propertyValueMap = new HashMap<String, Object>();
private static enum Type {
Color,
Alpha,
Factor,
Texture2DOrColor,
Texture2DOrAlpha,
Texture2DOrFactor,
Texture2D,
TextureCubeMap,
Ignore;
}
private static class FBXMaterialProperty {
private final String name;
private final Type type;
public FBXMaterialProperty(String name, Type type) {
this.name = name;
this.type = type;
}
}
private static boolean isValueAcceptable(Type type, Object value) {
if (type == Type.Ignore) {
return true;
}
if (value instanceof FbxTexture) {
switch (type) {
case Texture2D:
case Texture2DOrAlpha:
case Texture2DOrColor:
case Texture2DOrFactor:
return true;
}
} else if (value instanceof ColorRGBA) {
switch (type) {
case Color:
case Texture2DOrColor:
return true;
}
} else if (value instanceof Float) {
switch (type) {
case Alpha:
case Factor:
case Texture2DOrAlpha:
case Texture2DOrFactor:
return true;
}
}
return false;
}
private static void defineProp(String name, Type type) {
propertyMetaMap.put(name, new FBXMaterialProperty(name, type));
}
private static void defineAlias(String alias, String name) {
propertyMetaMap.put(alias, propertyMetaMap.get(name));
}
static {
// Lighting->Ambient
// TODO: Add support for AmbientMap??
defineProp("AmbientColor", Type.Color);
defineProp("AmbientFactor", Type.Factor);
defineAlias("Ambient", "AmbientColor");
// Lighting->DiffuseMap/Diffuse
defineProp("DiffuseColor", Type.Texture2DOrColor);
defineProp("DiffuseFactor", Type.Factor);
defineAlias("Diffuse", "DiffuseColor");
// Lighting->SpecularMap/Specular
defineProp("SpecularColor", Type.Texture2DOrColor);
defineProp("SpecularFactor", Type.Factor);
defineAlias("Specular", "SpecularColor");
// Lighting->AlphaMap/Diffuse
defineProp("TransparentColor", Type.Texture2DOrAlpha);
// Lighting->Diffuse
defineProp("TransparencyFactor", Type.Alpha);
defineAlias("Opacity", "TransparencyFactor");
// Lighting->GlowMap/GlowColor
defineProp("EmissiveColor", Type.Texture2DOrColor);
defineProp("EmissiveFactor", Type.Factor);
defineAlias("Emissive", "EmissiveColor");
// Lighting->Shininess
defineProp("Shininess", Type.Factor);
defineAlias("ShininessExponent", "Shininess");
// Lighting->NormalMap
defineProp("NormalMap", Type.Texture2D);
defineAlias("Normal", "NormalMap");
// Lighting->EnvMap
defineProp("ReflectionColor", Type.Texture2DOrColor);
// Lighting->FresnelParams
defineProp("Reflectivity", Type.Factor);
defineAlias("ReflectionFactor", "Reflectivity");
// ShadingModel is no longer specified under Properties element.
defineProp("ShadingModel", Type.Ignore);
// MultiLayer materials aren't supported anyway..
defineProp("MultiLayer", Type.Ignore);
// Not sure what this is.. NormalMap again??
defineProp("Bump", Type.Texture2DOrColor);
defineProp("BumpFactor", Type.Factor);
defineProp("DisplacementColor", Type.Color);
defineProp("DisplacementFactor", Type.Factor);
}
public void setPropertyTexture(String name, FbxTexture texture) {
FBXMaterialProperty prop = propertyMetaMap.get(name);
if (prop == null) {
logger.log(Level.WARNING, "Unknown FBX material property '{0}'", name);
return;
}
if (propertyValueMap.get(name) instanceof FbxTexture) {
// Multiple / layered textures ..
// Just write into 2nd slot for now (maybe will use for lightmaps).
name = name + "2";
}
propertyValueMap.put(name, texture);
}
public void setPropertyFromElement(FbxElement propertyElement) {
String name = (String) propertyElement.properties.get(0);
FBXMaterialProperty prop = propertyMetaMap.get(name);
if (prop == null) {
logger.log(Level.WARNING, "Unknown FBX material property ''{0}''", name);
return;
}
// It is either a color, alpha, or factor.
// Textures can only be set via setPropertyTexture.
// If it is an alias, get the real name of the property.
String realName = prop.name;
switch (prop.type) {
case Alpha:
case Factor:
case Texture2DOrFactor:
case Texture2DOrAlpha:
double value = (Double) propertyElement.properties.get(4);
propertyValueMap.put(realName, (float)value);
break;
case Color:
case Texture2DOrColor:
double x = (Double) propertyElement.properties.get(4);
double y = (Double) propertyElement.properties.get(5);
double z = (Double) propertyElement.properties.get(6);
ColorRGBA color = new ColorRGBA((float)x, (float)y, (float)z, 1f);
propertyValueMap.put(realName, color);
break;
default:
logger.log(Level.WARNING, "FBX material property ''{0}'' requires a texture.", name);
break;
}
}
public Object getProperty(String name) {
return propertyValueMap.get(name);
}
public static Type getPropertyType(String name) {
FBXMaterialProperty prop = propertyMetaMap.get(name);
if (prop == null) {
return null;
} else {
return prop.type;
}
}
}

@ -0,0 +1,146 @@
/*
* 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.material;
import com.jme3.asset.AssetManager;
import com.jme3.asset.TextureKey;
import com.jme3.math.Vector2f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapAxis;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.util.PlaceholderAssets;
public class FbxTexture extends FbxObject<Texture> {
private static enum AlphaSource {
None,
FromTextureAlpha,
FromTextureIntensity;
}
private String type;
private FbxImage media;
// TODO: not currently used.
private AlphaSource alphaSource = AlphaSource.FromTextureAlpha;
private String uvSet;
private int wrapModeU = 0, wrapModeV = 0;
private final Vector2f uvTranslation = new Vector2f(0, 0);
private final Vector2f uvScaling = new Vector2f(1, 1);
public FbxTexture(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
public String getUvSet() {
return uvSet;
}
@Override
protected Texture toJmeObject() {
Image image = null;
TextureKey key = null;
if (media != null) {
image = (Image) media.getJmeObject();
key = media.getTextureKey();
}
if (image == null) {
image = PlaceholderAssets.getPlaceholderImage(assetManager);
}
Texture2D tex = new Texture2D(image);
if (key != null) {
tex.setKey(key);
tex.setName(key.getName());
tex.setAnisotropicFilter(key.getAnisotropy());
}
tex.setMinFilter(MinFilter.Trilinear);
tex.setMagFilter(MagFilter.Bilinear);
if (wrapModeU == 0) {
tex.setWrap(WrapAxis.S, WrapMode.Repeat);
}
if (wrapModeV == 0) {
tex.setWrap(WrapAxis.T, WrapMode.Repeat);
}
return tex;
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
if (getSubclassName().equals("")) {
for (FbxElement e : element.children) {
if (e.id.equals("Type")) {
type = (String) e.properties.get(0);
}
/*else if (e.id.equals("FileName")) {
filename = (String) e.properties.get(0);
}*/
}
for (FbxElement prop : element.getFbxProperties()) {
String propName = (String) prop.properties.get(0);
if (propName.equals("AlphaSource")) {
// ???
} else if (propName.equals("UVSet")) {
uvSet = (String) prop.properties.get(4);
} else if (propName.equals("WrapModeU")) {
wrapModeU = (Integer) prop.properties.get(4);
} else if (propName.equals("WrapModeV")) {
wrapModeV = (Integer) prop.properties.get(4);
}
}
}
}
@Override
public void connectObject(FbxObject object) {
if (!(object instanceof FbxImage)) {
unsupportedConnectObject(object);
// } else if (media != null) {
// throw new UnsupportedOperationException("An image is already attached to this texture.");
}
this.media = (FbxImage) object;
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,107 @@
/*
* 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.mesh;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.Collection;
import java.util.EnumMap;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class FbxLayer {
private static final Logger logger = Logger.getLogger(FbxLayer.class.getName());
public static class FbxLayerElementRef {
FbxLayerElement.Type layerElementType;
int layerElementIndex;
FbxLayerElement layerElement;
}
int layer;
final EnumMap<FbxLayerElement.Type, FbxLayerElementRef> references =
new EnumMap<FbxLayerElement.Type, FbxLayerElementRef>(FbxLayerElement.Type.class);
private FbxLayer() { }
public Object getVertexData(FbxLayerElement.Type type, int polygonIndex,
int polygonVertexIndex, int positionIndex, int edgeIndex) {
FbxLayerElementRef reference = references.get(type);
if (reference == null) {
return null;
} else {
return reference.layerElement.getVertexData(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex);
}
}
public FbxLayerElement.Type[] getLayerElementTypes() {
FbxLayerElement.Type[] types = new FbxLayerElement.Type[references.size()];
references.keySet().toArray(types);
return types;
}
public void setLayerElements(Collection<FbxLayerElement> layerElements) {
for (FbxLayerElement layerElement : layerElements) {
FbxLayerElementRef reference = references.get(layerElement.type);
if (reference != null && reference.layerElementIndex == layerElement.index) {
reference.layerElement = layerElement;
}
}
}
public static FbxLayer fromElement(FbxElement element) {
FbxLayer layer = new FbxLayer();
layer.layer = (Integer)element.properties.get(0);
next_element: for (FbxElement child : element.children) {
if (!child.id.equals("LayerElement")) {
continue;
}
FbxLayerElementRef ref = new FbxLayerElementRef();
for (FbxElement child2 : child.children) {
if (child2.id.equals("Type")) {
String layerElementTypeStr = (String) child2.properties.get(0);
layerElementTypeStr = layerElementTypeStr.substring("LayerElement".length());
try {
ref.layerElementType = FbxLayerElement.Type.valueOf(layerElementTypeStr);
} catch (IllegalArgumentException ex) {
logger.log(Level.WARNING, "Unsupported layer type: {0}. Ignoring.", layerElementTypeStr);
continue next_element;
}
} else if (child2.id.equals("TypedIndex")) {
ref.layerElementIndex = (Integer) child2.properties.get(0);
}
}
layer.references.put(ref.layerElementType, ref);
}
return layer;
}
}

@ -0,0 +1,243 @@
/*
* 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.mesh;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxLayerElement {
private static final Logger logger = Logger.getLogger(FbxLayerElement.class.getName());
public enum Type {
Position, // Vector3f (isn't actually defined in FBX)
BoneIndex, // List<Integer> (isn't actually defined in FBX)
BoneWeight, // List<Float> isn't actually defined in FBX)
Normal, // Vector3f
Binormal, // Vector3f
Tangent, // Vector3f
UV, // Vector2f
TransparentUV, // Vector2f
Color, // ColorRGBA
Material, // Integer
Smoothing, // Integer
Visibility, // Integer
Texture, // ??? (FBX 6.x)
PolygonGroup, // ??? (FBX 6.x)
NormalMapTextures, // ??? (FBX 6.x)
SpecularFactorUV, // ??? (FBX 6.x)
NormalMapUV, // ??? (FBX 6.x)
SpecularFactorTextures, // ??? (FBX 6.x)
}
public enum MappingInformationType {
NoMappingInformation,
AllSame,
ByPolygonVertex,
ByVertex,
ByPolygon,
ByEdge;
}
public enum ReferenceInformationType {
Direct,
IndexToDirect;
}
public enum TextureBlendMode {
Translucent;
}
private static final Set<String> indexTypes = new HashSet<String>();
static {
indexTypes.add("UVIndex");
indexTypes.add("NormalsIndex");
indexTypes.add("TangentsIndex");
indexTypes.add("BinormalsIndex");
indexTypes.add("Smoothing");
indexTypes.add("Materials");
indexTypes.add("TextureId");
indexTypes.add("ColorIndex");
indexTypes.add("PolygonGroup");
}
int index;
Type type;
ReferenceInformationType refInfoType;
MappingInformationType mapInfoType;
String name = "";
Object[] data;
int[] dataIndices;
private FbxLayerElement() { }
public String toString() {
return "LayerElement[type=" + type + ", layer=" + index +
", mapInfoType=" + mapInfoType + ", refInfoType=" + refInfoType + "]";
}
private Object getVertexDataIndexToDirect(int polygonIndex, int polygonVertexIndex,
int positionIndex, int edgeIndex) {
switch (mapInfoType) {
case AllSame: return data[dataIndices[0]];
case ByPolygon: return data[dataIndices[polygonIndex]];
case ByPolygonVertex: return data[dataIndices[polygonVertexIndex]];
case ByVertex: return data[dataIndices[positionIndex]];
case ByEdge: return data[dataIndices[edgeIndex]];
default: throw new UnsupportedOperationException();
}
}
private Object getVertexDataDirect(int polygonIndex, int polygonVertexIndex,
int positionIndex, int edgeIndex) {
switch (mapInfoType) {
case AllSame: return data[0];
case ByPolygon: return data[polygonIndex];
case ByPolygonVertex: return data[polygonVertexIndex];
case ByVertex: return data[positionIndex];
case ByEdge: return data[edgeIndex];
default: throw new UnsupportedOperationException();
}
}
public Object getVertexData(int polygonIndex, int polygonVertexIndex, int positionIndex, int edgeIndex) {
switch (refInfoType) {
case Direct: return getVertexDataDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex);
case IndexToDirect: return getVertexDataIndexToDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex);
default: return null;
}
}
public static FbxLayerElement fromPositions(double[] positionData) {
FbxLayerElement layerElement = new FbxLayerElement();
layerElement.index = -1;
layerElement.name = "";
layerElement.type = Type.Position;
layerElement.mapInfoType = MappingInformationType.ByVertex;
layerElement.refInfoType = ReferenceInformationType.Direct;
layerElement.data = toVector3(positionData);
layerElement.dataIndices = null;
return layerElement;
}
public static FbxLayerElement fromElement(FbxElement element) {
FbxLayerElement layerElement = new FbxLayerElement();
if (!element.id.startsWith("LayerElement")) {
throw new IllegalArgumentException("Not a layer element");
}
layerElement.index = (Integer)element.properties.get(0);
String elementType = element.id.substring("LayerElement".length());
try {
layerElement.type = Type.valueOf(elementType);
} catch (IllegalArgumentException ex) {
logger.log(Level.WARNING, "Unsupported layer element: {0}. Ignoring.", elementType);
}
for (FbxElement child : element.children) {
if (child.id.equals("MappingInformationType")) {
String mapInfoTypeVal = (String) child.properties.get(0);
if (mapInfoTypeVal.equals("ByVertice")) {
mapInfoTypeVal = "ByVertex";
}
layerElement.mapInfoType = MappingInformationType.valueOf(mapInfoTypeVal);
} else if (child.id.equals("ReferenceInformationType")) {
String refInfoTypeVal = (String) child.properties.get(0);
if (refInfoTypeVal.equals("Index")) {
refInfoTypeVal = "IndexToDirect";
}
layerElement.refInfoType = ReferenceInformationType.valueOf(refInfoTypeVal);
} else if (child.id.equals("Normals") || child.id.equals("Tangents") || child.id.equals("Binormals")) {
layerElement.data = toVector3(FbxMeshUtil.getDoubleArray(child));
} else if (child.id.equals("Colors")) {
layerElement.data = toColorRGBA(FbxMeshUtil.getDoubleArray(child));
} else if (child.id.equals("UV")) {
layerElement.data = toVector2(FbxMeshUtil.getDoubleArray(child));
} else if (indexTypes.contains(child.id)) {
layerElement.dataIndices = FbxMeshUtil.getIntArray(child);
} else if (child.id.equals("Name")) {
layerElement.name = (String) child.properties.get(0);
}
}
if (layerElement.data == null) {
// For Smoothing / Materials, data = dataIndices
layerElement.refInfoType = ReferenceInformationType.Direct;
layerElement.data = new Integer[layerElement.dataIndices.length];
for (int i = 0; i < layerElement.data.length; i++) {
layerElement.data[i] = layerElement.dataIndices[i];
}
layerElement.dataIndices = null;
}
return layerElement;
}
static Vector3f[] toVector3(double[] data) {
Vector3f[] vectors = new Vector3f[data.length / 3];
for (int i = 0; i < vectors.length; i++) {
float x = (float) data[i * 3];
float y = (float) data[i * 3 + 1];
float z = (float) data[i * 3 + 2];
vectors[i] = new Vector3f(x, y, z);
}
return vectors;
}
static Vector2f[] toVector2(double[] data) {
Vector2f[] vectors = new Vector2f[data.length / 2];
for (int i = 0; i < vectors.length; i++) {
float x = (float) data[i * 2];
float y = (float) data[i * 2 + 1];
vectors[i] = new Vector2f(x, y);
}
return vectors;
}
static ColorRGBA[] toColorRGBA(double[] data) {
ColorRGBA[] colors = new ColorRGBA[data.length / 4];
for (int i = 0; i < colors.length; i++) {
float r = (float) data[i * 4];
float g = (float) data[i * 4 + 1];
float b = (float) data[i * 4 + 2];
float a = (float) data[i * 4 + 3];
colors[i] = new ColorRGBA(r, g, b, a);
}
return colors;
}
}

@ -0,0 +1,316 @@
/*
* 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.mesh;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.IrUtils;
import com.jme3.scene.plugins.IrBoneWeightIndex;
import com.jme3.scene.plugins.IrMesh;
import com.jme3.scene.plugins.IrPolygon;
import com.jme3.scene.plugins.IrVertex;
import com.jme3.scene.plugins.fbx.anim.FbxCluster;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.scene.plugins.fbx.node.FbxNodeAttribute;
import com.jme3.util.IntMap;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
private static final Logger logger = Logger.getLogger(FbxMesh.class.getName());
private FbxPolygon[] polygons;
private int[] edges;
private FbxLayerElement[] layerElements;
private Vector3f[] positions;
private FbxLayer[] layers;
private ArrayList<Integer>[] boneIndices;
private ArrayList<Float>[] boneWeights;
private FbxSkinDeformer skinDeformer;
public FbxMesh(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
List<FbxLayerElement> layerElementsList = new ArrayList<FbxLayerElement>();
List<FbxLayer> layersList = new ArrayList<FbxLayer>();
for (FbxElement e : element.children) {
if (e.id.equals("Vertices")) {
setPositions(FbxMeshUtil.getDoubleArray(e));
} else if (e.id.equals("PolygonVertexIndex")) {
setPolygonVertexIndices(FbxMeshUtil.getIntArray(e));
} else if (e.id.equals("Edges")) {
setEdges(FbxMeshUtil.getIntArray(e));
} else if (e.id.startsWith("LayerElement")) {
layerElementsList.add(FbxLayerElement.fromElement(e));
} else if (e.id.equals("Layer")) {
layersList.add(FbxLayer.fromElement(e));
}
}
for (FbxLayer layer : layersList) {
layer.setLayerElements(layerElementsList);
}
layerElements = new FbxLayerElement[layerElementsList.size()];
layerElementsList.toArray(layerElements);
layers = new FbxLayer[layersList.size()];
layersList.toArray(layers);
}
public FbxSkinDeformer getSkinDeformer() {
return skinDeformer;
}
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);
}
}
@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.");
return;
}
skinDeformer = (FbxSkinDeformer) object;
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
private void setPositions(double[] positions) {
this.positions = FbxLayerElement.toVector3(positions);
}
private void setEdges(int[] edges) {
this.edges = edges;
// TODO: ...
}
private void setPolygonVertexIndices(int[] polygonVertexIndices) {
List<FbxPolygon> polygonList = new ArrayList<FbxPolygon>();
boolean finishPolygon = false;
List<Integer> vertexIndices = new ArrayList<Integer>();
for (int i = 0; i < polygonVertexIndices.length; i++) {
int vertexIndex = polygonVertexIndices[i];
if (vertexIndex < 0) {
vertexIndex ^= -1;
finishPolygon = true;
}
vertexIndices.add(vertexIndex);
if (finishPolygon) {
finishPolygon = false;
polygonList.add(FbxPolygon.fromIndices(vertexIndices));
vertexIndices.clear();
}
}
polygons = new FbxPolygon[polygonList.size()];
polygonList.toArray(polygons);
}
private static IrBoneWeightIndex[] toBoneWeightIndices(List<Integer> boneIndices, List<Float> boneWeights) {
IrBoneWeightIndex[] boneWeightIndices = new IrBoneWeightIndex[boneIndices.size()];
for (int i = 0; i < boneIndices.size(); i++) {
boneWeightIndices[i] = new IrBoneWeightIndex(boneIndices.get(i), boneWeights.get(i));
}
return boneWeightIndices;
}
@Override
protected IntMap<Mesh> toJmeObject() {
// Load clusters from SkinDeformer
if (skinDeformer != null) {
for (FbxCluster cluster : skinDeformer.getJmeObject()) {
applyCluster(cluster);
}
}
IrMesh irMesh = toIRMesh();
// Trim bone weights to 4 weights per vertex.
IrUtils.trimBoneWeights(irMesh);
// Convert tangents / binormals to tangents with parity.
IrUtils.toTangentsWithParity(irMesh);
// Triangulate quads.
IrUtils.triangulate(irMesh);
// Split meshes by material indices.
IntMap<IrMesh> irMeshes = IrUtils.splitByMaterial(irMesh);
// Create a jME3 Mesh for each material index.
IntMap<Mesh> jmeMeshes = new IntMap<Mesh>();
for (IntMap.Entry<IrMesh> irMeshEntry : irMeshes) {
Mesh jmeMesh = IrUtils.convertIrMeshToJmeMesh(irMeshEntry.getValue());
jmeMeshes.put(irMeshEntry.getKey(), jmeMesh);
}
if (jmeMeshes.size() == 0) {
// When will this actually happen? Not sure.
logger.log(Level.WARNING, "Empty FBX mesh found (unusual).");
}
// IMPORTANT: If we have a -1 entry, those are triangles
// with no material indices.
// 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.");
}
return jmeMeshes;
}
/**
* Convert FBXMesh to IRMesh.
*/
public IrMesh toIRMesh() {
IrMesh newMesh = new IrMesh();
newMesh.polygons = new IrPolygon[polygons.length];
int polygonVertexIndex = 0;
int positionIndex = 0;
FbxLayer layer0 = layers[0];
FbxLayer layer1 = layers.length > 1 ? layers[1] : null;
for (int i = 0; i < polygons.length; i++) {
FbxPolygon polygon = polygons[i];
IrPolygon irPolygon = new IrPolygon();
irPolygon.vertices = new IrVertex[polygon.indices.length];
for (int j = 0; j < polygon.indices.length; j++) {
positionIndex = polygon.indices[j];
IrVertex irVertex = new IrVertex();
irVertex.pos = positions[positionIndex];
if (layer0 != null) {
irVertex.norm = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Normal, i, polygonVertexIndex, positionIndex, 0);
irVertex.tang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Tangent, i, polygonVertexIndex, positionIndex, 0);
irVertex.bitang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Binormal, i, polygonVertexIndex, positionIndex, 0);
irVertex.uv0 = (Vector2f) layer0.getVertexData(FbxLayerElement.Type.UV, i, polygonVertexIndex, positionIndex, 0);
irVertex.color = (ColorRGBA) layer0.getVertexData(FbxLayerElement.Type.Color, i, polygonVertexIndex, positionIndex, 0);
irVertex.material = (Integer) layer0.getVertexData(FbxLayerElement.Type.Material, i, polygonVertexIndex, positionIndex, 0);
irVertex.smoothing = (Integer) layer0.getVertexData(FbxLayerElement.Type.Smoothing, i, polygonVertexIndex, positionIndex, 0);
}
if (layer1 != null) {
irVertex.uv1 = (Vector2f) layer1.getVertexData(FbxLayerElement.Type.UV, i,
polygonVertexIndex, positionIndex, 0);
}
if (boneIndices != null) {
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex];
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex];
if (boneIndicesForVertex != null) {
irVertex.boneWeightsIndices = toBoneWeightIndices(boneIndicesForVertex, boneWeightsForVertex);
}
}
irPolygon.vertices[j] = irVertex;
polygonVertexIndex++;
}
newMesh.polygons[i] = irPolygon;
}
// Ensure "inspection vertex" specifies that mesh has bone indices / weights
if (boneIndices != null && newMesh.polygons[0].vertices[0] == null) {
newMesh.polygons[0].vertices[0].boneWeightsIndices = new IrBoneWeightIndex[0];
}
return newMesh;
}
}

@ -0,0 +1,69 @@
/*
* 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.mesh;
import com.jme3.scene.plugins.fbx.file.FbxElement;
public class FbxMeshUtil {
public static double[] getDoubleArray(FbxElement el) {
if (el.propertiesTypes[0] == 'd') {
// FBX 7.x
return (double[]) el.properties.get(0);
} else if (el.propertiesTypes[0] == 'D') {
// FBX 6.x
double[] doubles = new double[el.propertiesTypes.length];
for (int i = 0; i < doubles.length; i++) {
doubles[i] = (Double) el.properties.get(i);
}
return doubles;
} else {
return null;
}
}
public static int[] getIntArray(FbxElement el) {
if (el.propertiesTypes[0] == 'i') {
// FBX 7.x
return (int[]) el.properties.get(0);
} else if (el.propertiesTypes[0] == 'I') {
// FBX 6.x
int[] ints = new int[el.propertiesTypes.length];
for (int i = 0; i < ints.length; i++) {
ints[i] = (Integer) el.properties.get(i);
}
return ints;
} else {
return null;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2014 jMonkeyEngine
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -29,36 +29,31 @@
* 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.file;
package com.jme3.scene.plugins.fbx.mesh;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FBXElement {
public String id;
public List<Object> properties;
/*
* Y - signed short
* C - boolean
* I - signed integer
* F - float
* D - double
* L - long
* R - byte array
* S - string
* f - array of floats
* i - array of ints
* d - array of doubles
* l - array of longs
* b - array of boleans
* c - array of unsigned bytes (represented as array of ints)
*/
public char[] propertiesTypes;
public List<FBXElement> children = new ArrayList<FBXElement>();
public FBXElement(int propsCount) {
properties = new ArrayList<Object>(propsCount);
propertiesTypes = new char[propsCount];
}
public final class FbxPolygon {
int[] indices;
@Override
public String toString() {
return Arrays.toString(indices);
}
private static int[] listToArray(List<Integer> indices) {
int[] indicesArray = new int[indices.size()];
for (int i = 0; i < indices.size(); i++) {
indicesArray[i] = indices.get(i);
}
return indicesArray;
}
public static FbxPolygon fromIndices(List<Integer> indices) {
FbxPolygon poly = new FbxPolygon();
poly.indices = listToArray(indices);
return poly;
}
}

@ -0,0 +1,145 @@
/*
* 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.misc;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxGlobalSettings {
private static final Logger logger = Logger.getLogger(FbxGlobalSettings.class.getName());
private static final Map<Integer, Float> timeModeToFps = new HashMap<Integer, Float>();
static {
timeModeToFps.put(1, 120f);
timeModeToFps.put(2, 100f);
timeModeToFps.put(3, 60f);
timeModeToFps.put(4, 50f);
timeModeToFps.put(5, 48f);
timeModeToFps.put(6, 30f);
timeModeToFps.put(9, 30f / 1.001f);
timeModeToFps.put(10, 25f);
timeModeToFps.put(11, 24f);
timeModeToFps.put(13, 24f / 1.001f);
timeModeToFps.put(14, -1f);
timeModeToFps.put(15, 96f);
timeModeToFps.put(16, 72f);
timeModeToFps.put(17, 60f / 1.001f);
}
public float unitScaleFactor = 1.0f;
public ColorRGBA ambientColor = ColorRGBA.Black;
public float frameRate = 25.0f;
/**
* @return A {@link Transform} that converts from the FBX file coordinate
* system to jME3 coordinate system.
* jME3's coordinate system is:
* <ul>
* <li>Units are specified in meters.</li>
* <li>Orientation is right-handed with Y-up.</li>
* </ul>
*/
public Transform getGlobalTransform() {
// Default unit scale factor is 1 (centimeters),
// convert to meters.
float scale = unitScaleFactor / 100.0f;
// TODO: handle rotation
return new Transform(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(scale, scale, scale));
}
public void fromElement(FbxElement element) {
// jME3 uses a +Y up, -Z forward coordinate system (same as OpenGL)
// Luckily enough, this is also the default for FBX models.
int timeMode = -1;
float customFrameRate = 30.0f;
for (FbxElement e2 : element.getFbxProperties()) {
String propName = (String) e2.properties.get(0);
if (propName.equals("UnitScaleFactor")) {
unitScaleFactor = ((Double) e2.properties.get(4)).floatValue();
if (unitScaleFactor != 100.0f) {
logger.log(Level.WARNING, "FBX model isn't using meters for world units. Scale could be incorrect.");
}
} else if (propName.equals("TimeMode")) {
timeMode = (Integer) e2.properties.get(4);
} else if (propName.equals("CustomFrameRate")) {
float framerate = ((Double) e2.properties.get(4)).floatValue();
if (framerate != -1) {
customFrameRate = framerate;
}
} else if (propName.equals("UpAxis")) {
Integer upAxis = (Integer) e2.properties.get(4);
if (upAxis != 1) {
logger.log(Level.WARNING, "FBX model isn't using Y as up axis. Orientation could be incorrect");
}
} else if (propName.equals("UpAxisSign")) {
Integer upAxisSign = (Integer) e2.properties.get(4);
if (upAxisSign != 1) {
logger.log(Level.WARNING, "FBX model isn't using correct up axis sign. Orientation could be incorrect");
}
} else if (propName.equals("FrontAxis")) {
Integer frontAxis = (Integer) e2.properties.get(4);
if (frontAxis != 2) {
logger.log(Level.WARNING, "FBX model isn't using Z as forward axis. Orientation could be incorrect");
}
} else if (propName.equals("FrontAxisSign")) {
Integer frontAxisSign = (Integer) e2.properties.get(4);
if (frontAxisSign != -1) {
logger.log(Level.WARNING, "FBX model isn't using correct forward axis sign. Orientation could be incorrect");
}
}
}
Float fps = timeModeToFps.get(timeMode);
if (fps != null) {
if (fps == -1f) {
// Using custom framerate
frameRate = customFrameRate;
} else {
// Use FPS from time mode.
frameRate = fps;
}
}
}
}

@ -0,0 +1,617 @@
/*
* 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.node;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
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.FbxSkinDeformer;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.material.FbxImage;
import com.jme3.scene.plugins.fbx.material.FbxMaterial;
import com.jme3.scene.plugins.fbx.material.FbxTexture;
import com.jme3.scene.plugins.fbx.mesh.FbxMesh;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.util.IntMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxNode extends FbxObject<Spatial> {
private static final Logger logger = Logger.getLogger(FbxNode.class.getName());
private static enum InheritMode {
/**
* Apply parent scale after child rotation.
* This is the only mode correctly supported by jME3.
*/
ScaleAfterChildRotation,
/**
* Apply parent scale before child rotation.
* Not supported by jME3, will cause distortion with
* non-uniform scale. No way around it.
*/
ScaleBeforeChildRotation,
/**
* Do not apply parent scale at all.
* Not supported by jME3, will cause distortion.
* Could be worked around by via:
* <code>jmeChildScale = jmeParentScale / fbxChildScale</code>
*/
NoParentScale
}
private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation;
protected FbxNode parent;
protected List<FbxNode> children = new ArrayList<FbxNode>();
protected List<FbxMaterial> materials = new ArrayList<FbxMaterial>();
protected Map<String, Object> userData = new HashMap<String, Object>();
protected Map<String, List<FbxAnimCurveNode>> propertyToAnimCurveMap = new HashMap<String, List<FbxAnimCurveNode>>();
protected FbxNodeAttribute nodeAttribute;
protected double visibility = 1.0;
/**
* For FBX nodes that contain a skeleton (i.e. FBX limbs).
*/
protected Skeleton skeleton;
protected final Transform jmeWorldNodeTransform = new Transform();
protected final Transform jmeLocalNodeTransform = new Transform();
// optional - used for limbs / bones / skeletons
protected Transform jmeWorldBindPose;
protected Transform jmeLocalBindPose;
// used for debugging only
protected Matrix4f cachedWorldBindPose;
public FbxNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
public Transform computeFbxLocalTransform() {
// TODO: implement the actual algorithm, which is this:
// Render Local Translation =
// Inv Scale Pivot * Lcl Scale * Scale Pivot * Scale Offset * Inv Rota Pivot * Post Rotation * Rotation * Pre Rotation * Rotation Pivot * Rotation Offset * Translation
// LclTranslation,
// LclRotation,
// PreRotation,
// PostRotation,
// RotationPivot,
// RotationOffset,
// LclScaling,
// ScalingPivot,
// ScalingOffset
Matrix4f scaleMat = new Matrix4f();
scaleMat.setScale(jmeLocalNodeTransform.getScale());
Matrix4f rotationMat = new Matrix4f();
rotationMat.setRotationQuaternion(jmeLocalNodeTransform.getRotation());
Matrix4f translationMat = new Matrix4f();
translationMat.setTranslation(jmeLocalNodeTransform.getTranslation());
Matrix4f result = new Matrix4f();
result.multLocal(scaleMat).multLocal(rotationMat).multLocal(translationMat);
Transform t = new Transform();
t.fromTransformMatrix(result);
return t;
}
public void setWorldBindPose(Matrix4f worldBindPose) {
if (cachedWorldBindPose != null) {
if (!cachedWorldBindPose.equals(worldBindPose)) {
throw new UnsupportedOperationException("Bind poses don't match");
}
}
cachedWorldBindPose = worldBindPose;
this.jmeWorldBindPose = new Transform();
this.jmeWorldBindPose.setTranslation(worldBindPose.toTranslationVector());
this.jmeWorldBindPose.setRotation(worldBindPose.toRotationQuat());
this.jmeWorldBindPose.setScale(worldBindPose.toScaleVector());
System.out.println("\tBind Pose for " + getName());
System.out.println(jmeWorldBindPose);
float[] angles = new float[3];
jmeWorldBindPose.getRotation().toAngles(angles);
System.out.println("Angles: " + angles[0] * FastMath.RAD_TO_DEG + ", " +
angles[1] * FastMath.RAD_TO_DEG + ", " +
angles[2] * FastMath.RAD_TO_DEG);
}
public void updateWorldTransforms(Transform jmeParentNodeTransform, Transform parentBindPose) {
Transform fbxLocalTransform = computeFbxLocalTransform();
jmeLocalNodeTransform.set(fbxLocalTransform);
if (jmeParentNodeTransform != null) {
jmeParentNodeTransform = jmeParentNodeTransform.clone();
switch (inheritMode) {
case NoParentScale:
case ScaleAfterChildRotation:
case ScaleBeforeChildRotation:
jmeWorldNodeTransform.set(jmeLocalNodeTransform);
jmeWorldNodeTransform.combineWithParent(jmeParentNodeTransform);
break;
}
} else {
jmeWorldNodeTransform.set(jmeLocalNodeTransform);
}
if (jmeWorldBindPose != null) {
jmeLocalBindPose = new Transform();
// Need to derive local bind pose from world bind pose
// (this is to be expected for FBX limbs)
jmeLocalBindPose.set(jmeWorldBindPose);
jmeLocalBindPose.combineWithParent(parentBindPose.invert());
// Its somewhat odd for the transforms to differ ...
System.out.println("Bind Pose for: " + getName());
if (!jmeLocalBindPose.equals(jmeLocalNodeTransform)) {
System.out.println("Local Bind: " + jmeLocalBindPose);
System.out.println("Local Trans: " + jmeLocalNodeTransform);
}
if (!jmeWorldBindPose.equals(jmeWorldNodeTransform)) {
System.out.println("World Bind: " + jmeWorldBindPose);
System.out.println("World Trans: " + jmeWorldNodeTransform);
}
} else {
// World pose derived from local transforms
// (this is to be expected for FBX nodes)
jmeLocalBindPose = new Transform();
jmeWorldBindPose = new Transform();
jmeLocalBindPose.set(jmeLocalNodeTransform);
if (parentBindPose != null) {
jmeWorldBindPose.set(jmeLocalNodeTransform);
jmeWorldBindPose.combineWithParent(parentBindPose);
} else {
jmeWorldBindPose.set(jmeWorldNodeTransform);
}
}
for (FbxNode child : children) {
child.updateWorldTransforms(jmeWorldNodeTransform, jmeWorldBindPose);
}
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
Vector3f localTranslation = new Vector3f();
Quaternion localRotation = new Quaternion();
Vector3f localScale = new Vector3f(Vector3f.UNIT_XYZ);
Quaternion preRotation = new Quaternion();
for (FbxElement e2 : element.getFbxProperties()) {
String propName = (String) e2.properties.get(0);
String type = (String) e2.properties.get(3);
if (propName.equals("Lcl Translation")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
localTranslation.set((float) x, (float) y, (float) z); //.divideLocal(unitSize);
} else if (propName.equals("Lcl Rotation")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
localRotation.fromAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD);
} else if (propName.equals("Lcl Scaling")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
localScale.set((float) x, (float) y, (float) z); //.multLocal(unitSize);
} else if (propName.equals("PreRotation")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
preRotation.set(FbxNodeUtil.quatFromBoneAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD));
} else if (propName.equals("InheritType")) {
int inheritType = (Integer) e2.properties.get(4);
inheritMode = InheritMode.values()[inheritType];
} else if (propName.equals("Visibility")) {
visibility = (Double) e2.properties.get(4);
} else if (type.contains("U")) {
String userDataKey = (String) e2.properties.get(0);
String userDataType = (String) e2.properties.get(1);
Object userDataValue;
if (userDataType.equals("KString")) {
userDataValue = (String) e2.properties.get(4);
} else if (userDataType.equals("int")) {
userDataValue = (Integer) e2.properties.get(4);
} else if (userDataType.equals("double")) {
// NOTE: jME3 does not support doubles in UserData.
// Need to convert to float.
userDataValue = ((Double) e2.properties.get(4)).floatValue();
} else if (userDataType.equals("Vector")) {
float x = ((Double) e2.properties.get(4)).floatValue();
float y = ((Double) e2.properties.get(5)).floatValue();
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);
continue;
}
userData.put(userDataKey, userDataValue);
}
}
// Create local transform
// TODO: take into account Maya-style transforms (pre / post rotation ..)
jmeLocalNodeTransform.setTranslation(localTranslation);
jmeLocalNodeTransform.setRotation(localRotation);
jmeLocalNodeTransform.setScale(localScale);
if (element.getChildById("Vertices") != null) {
// This is an old-style FBX 6.1
// Meshes could be embedded inside the node..
// Inject the mesh into ourselves..
FbxMesh mesh = new FbxMesh(assetManager, sceneFolderName);
mesh.fromElement(element);
connectObject(mesh);
}
}
private Spatial tryCreateGeometry(int materialIndex, Mesh jmeMesh, boolean single) {
// Map meshes without material indices to material 0.
if (materialIndex == -1) {
materialIndex = 0;
}
Material jmeMat;
if (materialIndex >= materials.size()) {
// Material index does not exist. Create default material.
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
jmeMat.setReceivesShadows(true);
} else {
FbxMaterial fbxMat = materials.get(materialIndex);
jmeMat = fbxMat.getJmeObject();
}
String geomName = getName();
if (single) {
geomName += "-submesh";
} else {
geomName += "-mat-" + materialIndex + "-submesh";
}
Spatial spatial = new Geometry(geomName, jmeMesh);
spatial.setMaterial(jmeMat);
if (jmeMat.isTransparent()) {
spatial.setQueueBucket(Bucket.Transparent);
}
if (jmeMat.isReceivesShadows()) {
spatial.setShadowMode(ShadowMode.Receive);
}
spatial.updateModelBound();
return spatial;
}
/**
* If this geometry node is deformed by a skeleton, this
* returns the node containing the skeleton.
*
* In jME3, a mesh can be deformed by a skeleton only if it is
* a child of the node containing the skeleton. However, this
* is not a requirement in FBX, so we have to modify the scene graph
* of the loaded model to adjust for this.
* This happens automatically in
* {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}.
*
* @return The model this node would like to be a child of, or null
* if no preferred parent.
*/
public FbxNode getPreferredParent() {
if (!(nodeAttribute instanceof FbxMesh)) {
return null;
}
FbxMesh fbxMesh = (FbxMesh) nodeAttribute;
FbxSkinDeformer deformer = fbxMesh.getSkinDeformer();
FbxNode preferredParent = null;
if (deformer != null) {
for (FbxCluster cluster : deformer.getJmeObject()) {
FbxLimbNode limb = cluster.getLimb();
if (preferredParent == null) {
preferredParent = limb.getSkeletonHolder();
} else if (preferredParent != limb.getSkeletonHolder()) {
logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. "
+ "Only one skeleton will work, ignoring other skeletons.");
}
}
}
return preferredParent;
}
@Override
public Spatial toJmeObject() {
Spatial spatial;
if (nodeAttribute instanceof FbxMesh) {
FbxMesh fbxMesh = (FbxMesh) nodeAttribute;
IntMap<Mesh> jmeMeshes = fbxMesh.getJmeObject();
if (jmeMeshes == null || jmeMeshes.size() == 0) {
// No meshes found on FBXMesh (??)
logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node.");
spatial = new Node(getName() + "-node");
} else {
// Multiple jME3 geometries required for a single FBXMesh.
String nodeName;
if (children.isEmpty()) {
nodeName = getName() + "-mesh";
} else {
nodeName = getName() + "-node";
}
Node node = new Node(nodeName);
boolean singleMesh = jmeMeshes.size() == 1;
for (IntMap.Entry<Mesh> meshInfo : jmeMeshes) {
node.attachChild(tryCreateGeometry(meshInfo.getKey(), meshInfo.getValue(), singleMesh));
}
spatial = node;
}
} else {
if (nodeAttribute != null) {
// Just specifies that this is a "null" node.
nodeAttribute.getJmeObject();
}
// TODO: handle other node attribute types.
// right now everything we don't know about gets converted
// to jME3 Node.
spatial = new Node(getName() + "-node");
}
if (!children.isEmpty()) {
// Check uniform scale.
// Although, if inheritType is 0 (eInheritRrSs)
// it might not be a problem.
Vector3f localScale = jmeLocalNodeTransform.getScale();
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.");
}
}
spatial.setLocalTransform(jmeLocalNodeTransform);
if (visibility == 0.0) {
spatial.setCullHint(CullHint.Always);
}
for (Map.Entry<String, Object> userDataEntry : userData.entrySet()) {
spatial.setUserData(userDataEntry.getKey(), userDataEntry.getValue());
}
return spatial;
}
/**
* Create jME3 Skeleton objects on the scene.
*
* Goes through the scene graph and finds limbs that are
* attached to FBX nodes, then creates a Skeleton on the node
* based on the child limbs.
*
* Must be called prior to calling
* {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}.
*
* @param fbxNode The root FBX node.
*/
public static void createSkeletons(FbxNode fbxNode) {
boolean createSkeleton = false;
for (FbxNode fbxChild : fbxNode.children) {
if (fbxChild instanceof FbxLimbNode) {
createSkeleton = true;
} else {
createSkeletons(fbxChild);
}
}
if (createSkeleton) {
if (fbxNode.skeleton != null) {
throw new UnsupportedOperationException();
}
fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
System.out.println("created skeleton: " + fbxNode.skeleton);
}
}
private static void relocateSpatial(Spatial spatial,
Transform originalWorldTransform, Transform newWorldTransform) {
Transform localTransform = new Transform();
localTransform.set(originalWorldTransform);
localTransform.combineWithParent(newWorldTransform.invert());
spatial.setLocalTransform(localTransform);
}
public static Spatial createScene(FbxNode fbxNode) {
Spatial jmeSpatial = fbxNode.getJmeObject();
if (jmeSpatial instanceof Node) {
// Attach children to Node
Node jmeNode = (Node) jmeSpatial;
for (FbxNode fbxChild : fbxNode.children) {
if (!(fbxChild instanceof FbxLimbNode)) {
createScene(fbxChild);
FbxNode preferredParent = fbxChild.getPreferredParent();
Spatial jmeChild = fbxChild.getJmeObject();
if (preferredParent != null) {
System.out.println("Preferred parent for " + fbxChild + " is " + preferredParent);
Node jmePreferredParent = (Node) preferredParent.getJmeObject();
relocateSpatial(jmeChild, fbxChild.jmeWorldNodeTransform,
preferredParent.jmeWorldNodeTransform);
jmePreferredParent.attachChild(jmeChild);
} else {
jmeNode.attachChild(jmeChild);
}
}
}
}
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;
}
// public SceneLoader.Limb toLimb() {
// SceneLoader.Limb limb = new SceneLoader.Limb();
// limb.name = getName();
// Quaternion rotation = preRotation.mult(localRotation);
// limb.bindTransform = new Transform(localTranslation, rotation, localScale);
// return limb;
// }
public Skeleton getJmeSkeleton() {
return skeleton;
}
public List<FbxNode> getChildren() {
return children;
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxNode) {
// Scene Graph Object
FbxNode childNode = (FbxNode) object;
if (childNode.parent != null) {
throw new IllegalStateException("Cannot attach " + childNode
+ " to " + this + ". It is already "
+ "attached to " + childNode.parent);
}
childNode.parent = this;
children.add(childNode);
} else if (object instanceof FbxNodeAttribute) {
// Node Attribute
if (nodeAttribute != null) {
throw new IllegalStateException("An FBXNodeAttribute (" + nodeAttribute + ")" +
" is already attached to " + this + ". " +
"Only one attribute allowed per node.");
}
nodeAttribute = (FbxNodeAttribute) object;
if (nodeAttribute instanceof FbxNullAttribute) {
nodeAttribute.getJmeObject();
}
} else if (object instanceof FbxMaterial) {
materials.add((FbxMaterial) object);
} else if (object instanceof FbxImage || object instanceof FbxTexture) {
// Ignore - attaching textures to nodes is legacy feature.
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
// Only allowed to connect local transform properties to object
// (FbxAnimCurveNode)
if (object instanceof FbxAnimCurveNode) {
FbxAnimCurveNode curveNode = (FbxAnimCurveNode) object;
if (property.equals("Lcl Translation")
|| property.equals("Lcl Rotation")
|| property.equals("Lcl Scaling")) {
List<FbxAnimCurveNode> curveNodes = propertyToAnimCurveMap.get(property);
if (curveNodes == null) {
curveNodes = new ArrayList<FbxAnimCurveNode>();
curveNodes.add(curveNode);
propertyToAnimCurveMap.put(property, curveNodes);
}
curveNodes.add(curveNode);
// Make sure the curve knows about it animating
// this node as well.
curveNode.addInfluencedNode(this, property);
} else {
logger.log(Level.WARNING, "Animating the property ''{0}'' is not "
+ "supported. Ignoring.", property);
}
} else {
unsupportedConnectObjectProperty(object, property);
}
}
}

@ -0,0 +1,41 @@
/*
* 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.node;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
public abstract class FbxNodeAttribute<JT> extends FbxObject<JT> {
public FbxNodeAttribute(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
}

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

Loading…
Cancel
Save