diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java index 2a7b54023..62741253a 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java @@ -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(); diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index 9472bff0b..991ad25c0 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -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); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java index c4c0f3995..baad952a0 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java @@ -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) { diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index 819a77383..a78b4b931 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -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()} */ diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 5e230dabb..d9d944057 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -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). diff --git a/jme3-core/src/main/java/com/jme3/math/Transform.java b/jme3-core/src/main/java/com/jme3/math/Transform.java index 79992ed2b..ac7a324d0 100644 --- a/jme3-core/src/main/java/com/jme3/math/Transform.java +++ b/jme3-core/src/main/java/com/jme3/math/Transform.java @@ -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. */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index f0d42c8f2..9387b687e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -337,7 +337,19 @@ public enum Caps { *

* 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 diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index 9c73838e2..76eedb521 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java @@ -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); diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java index 50eb065ab..190ed4547 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java @@ -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+ } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java index e13402bd5..a0511f6ad 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java @@ -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(); + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java index 259c56406..2348bd3cd 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java @@ -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(); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java index b0e0b5f78..27f0eb8bd 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java @@ -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); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java index 252619db8..737019ce2 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java @@ -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); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index 423ae909e..a7ef9f52d 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -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); diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 9ae1c8cc7..db9b743f0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -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 loadExtensions(String extensions) { + private HashSet loadExtensions() { HashSet extensionSet = new HashSet(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); diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java new file mode 100644 index 000000000..7e6833b8b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java @@ -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> { + @Override + public int compare(Map.Entry o1, Map.Entry 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[] callTimes = new Map.Entry[state.callTiming.size()]; + int i = 0; + for (Map.Entry callTime : state.callTiming.entrySet()) { + callTimes[i++] = callTime; + } + Arrays.sort(callTimes, new CallTimingComparator()); + int limit = 10; + for (Map.Entry 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 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; + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java new file mode 100644 index 000000000..ca25810d4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java @@ -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 callTiming = new HashMap(); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java index b69d524b4..1b0d70749 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java @@ -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 constMap = generateConstantMap(GL.class, GLExt.class); + IntMap 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 constMap = generateConstantMap(GL2.class, GLExt.class); + IntMap constMap = generateConstantMap(GL2.class, GL3.class, GL4.class, GLFbo.class, GLExt.class); return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), glInterfaceClasses, new GLTracer(glInterface, constMap)); diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java index ed53e89bc..a7a53b915 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -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(); } diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 2bbf746ff..96ab62819 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -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(); diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag index 5d207cf83..26b68a533 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -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; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index 62b206b7e..6ad224d9b 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -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 diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 5d7f5ca8c..0c81c3e14 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -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{ diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java index 9c71aa132..726cf3e19 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java @@ -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){ diff --git a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java index 75d6b1c86..7e9012b6d 100644 --- a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java @@ -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()); diff --git a/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg index 715eab985..e9d79a459 100644 --- a/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg +++ b/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg @@ -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 diff --git a/jme3-ios/src/main/java/com/jme3/audio/android/AL.java b/jme3-ios/src/main/java/com/jme3/audio/android/AL.java deleted file mode 100644 index d8fea3933..000000000 --- a/jme3-ios/src/main/java/com/jme3/audio/android/AL.java +++ /dev/null @@ -1,1054 +0,0 @@ -package com.jme3.audio.android; - -/** - * - * @author iwgeric - */ -public class AL { - - - - /* ********** */ - /* FROM ALC.h */ - /* ********** */ - -// typedef struct ALCdevice_struct ALCdevice; -// typedef struct ALCcontext_struct ALCcontext; - - - /** - * No error - */ - static final int ALC_NO_ERROR = 0; - - /** - * No device - */ - static final int ALC_INVALID_DEVICE = 0xA001; - - /** - * invalid context ID - */ - static final int ALC_INVALID_CONTEXT = 0xA002; - - /** - * bad enum - */ - static final int ALC_INVALID_ENUM = 0xA003; - - /** - * bad value - */ - static final int ALC_INVALID_VALUE = 0xA004; - - /** - * Out of memory. - */ - static final int ALC_OUT_OF_MEMORY = 0xA005; - - - /** - * The Specifier string for default device - */ - static final int ALC_DEFAULT_DEVICE_SPECIFIER = 0x1004; - static final int ALC_DEVICE_SPECIFIER = 0x1005; - static final int ALC_EXTENSIONS = 0x1006; - - static final int ALC_MAJOR_VERSION = 0x1000; - static final int ALC_MINOR_VERSION = 0x1001; - - static final int ALC_ATTRIBUTES_SIZE = 0x1002; - static final int ALC_ALL_ATTRIBUTES = 0x1003; - - - /** - * Capture extension - */ - static final int ALC_EXT_CAPTURE = 1; - static final int ALC_CAPTURE_DEVICE_SPECIFIER = 0x310; - static final int ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x311; - static final int ALC_CAPTURE_SAMPLES = 0x312; - - - /** - * ALC_ENUMERATE_ALL_EXT enums - */ - static final int ALC_ENUMERATE_ALL_EXT = 1; - static final int ALC_DEFAULT_ALL_DEVICES_SPECIFIER = 0x1012; - static final int ALC_ALL_DEVICES_SPECIFIER = 0x1013; - - - /* ********** */ - /* FROM AL.h */ - /* ********** */ - -/** Boolean False. */ - static final int AL_FALSE = 0; - -/** Boolean True. */ - static final int AL_TRUE = 1; - -/* "no distance model" or "no buffer" */ - static final int AL_NONE = 0; - -/** Indicate Source has relative coordinates. */ - static final int AL_SOURCE_RELATIVE = 0x202; - - - -/** - * Directional source, inner cone angle, in degrees. - * Range: [0-360] - * Default: 360 - */ - static final int AL_CONE_INNER_ANGLE = 0x1001; - -/** - * Directional source, outer cone angle, in degrees. - * Range: [0-360] - * Default: 360 - */ - static final int AL_CONE_OUTER_ANGLE = 0x1002; - -/** - * Specify the pitch to be applied at source. - * Range: [0.5-2.0] - * Default: 1.0 - */ - static final int AL_PITCH = 0x1003; - -/** - * Specify the current location in three dimensional space. - * OpenAL, like OpenGL, uses a right handed coordinate system, - * where in a frontal default view X (thumb) points right, - * Y points up (index finger), and Z points towards the - * viewer/camera (middle finger). - * To switch from a left handed coordinate system, flip the - * sign on the Z coordinate. - * Listener position is always in the world coordinate system. - */ - static final int AL_POSITION = 0x1004; - -/** Specify the current direction. */ - static final int AL_DIRECTION = 0x1005; - -/** Specify the current velocity in three dimensional space. */ - static final int AL_VELOCITY = 0x1006; - -/** - * Indicate whether source is looping. - * Type: ALboolean? - * Range: [AL_TRUE, AL_FALSE] - * Default: FALSE. - */ - static final int AL_LOOPING = 0x1007; - -/** - * Indicate the buffer to provide sound samples. - * Type: ALuint. - * Range: any valid Buffer id. - */ - static final int AL_BUFFER = 0x1009; - -/** - * Indicate the gain (volume amplification) applied. - * Type: ALfloat. - * Range: ]0.0- ] - * A value of 1.0 means un-attenuated/unchanged. - * Each division by 2 equals an attenuation of -6dB. - * Each multiplicaton with 2 equals an amplification of +6dB. - * A value of 0.0 is meaningless with respect to a logarithmic - * scale; it is interpreted as zero volume - the channel - * is effectively disabled. - */ - static final int AL_GAIN = 0x100A; - -/* - * Indicate minimum source attenuation - * Type: ALfloat - * Range: [0.0 - 1.0] - * - * Logarthmic - */ - static final int AL_MIN_GAIN = 0x100D; - -/** - * Indicate maximum source attenuation - * Type: ALfloat - * Range: [0.0 - 1.0] - * - * Logarthmic - */ - static final int AL_MAX_GAIN = 0x100E; - -/** - * Indicate listener orientation. - * - * at/up - */ - static final int AL_ORIENTATION = 0x100F; - -/** - * Source state information. - */ - static final int AL_SOURCE_STATE = 0x1010; - static final int AL_INITIAL = 0x1011; - static final int AL_PLAYING = 0x1012; - static final int AL_PAUSED = 0x1013; - static final int AL_STOPPED = 0x1014; - -/** - * Buffer Queue params - */ - static final int AL_BUFFERS_QUEUED = 0x1015; - static final int AL_BUFFERS_PROCESSED = 0x1016; - -/** - * Source buffer position information - */ - static final int AL_SEC_OFFSET = 0x1024; - static final int AL_SAMPLE_OFFSET = 0x1025; - static final int AL_BYTE_OFFSET = 0x1026; - -/* - * Source type (Static, Streaming or undetermined) - * Source is Static if a Buffer has been attached using AL_BUFFER - * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers - * Source is undetermined when it has the NULL buffer attached - */ - static final int AL_SOURCE_TYPE = 0x1027; - static final int AL_STATIC = 0x1028; - static final int AL_STREAMING = 0x1029; - static final int AL_UNDETERMINED = 0x1030; - -/** Sound samples: format specifier. */ - static final int AL_FORMAT_MONO8 = 0x1100; - static final int AL_FORMAT_MONO16 = 0x1101; - static final int AL_FORMAT_STEREO8 = 0x1102; - static final int AL_FORMAT_STEREO16 = 0x1103; - -/** - * source specific reference distance - * Type: ALfloat - * Range: 0.0 - +inf - * - * At 0.0, no distance attenuation occurs. Default is - * 1.0. - */ - static final int AL_REFERENCE_DISTANCE = 0x1020; - -/** - * source specific rolloff factor - * Type: ALfloat - * Range: 0.0 - +inf - * - */ - static final int AL_ROLLOFF_FACTOR = 0x1021; - -/** - * Directional source, outer cone gain. - * - * Default: 0.0 - * Range: [0.0 - 1.0] - * Logarithmic - */ - static final int AL_CONE_OUTER_GAIN = 0x1022; - -/** - * Indicate distance above which sources are not - * attenuated using the inverse clamped distance model. - * - * Default: +inf - * Type: ALfloat - * Range: 0.0 - +inf - */ - static final int AL_MAX_DISTANCE = 0x1023; - -/** - * Sound samples: frequency, in units of Hertz [Hz]. - * This is the number of samples per second. Half of the - * sample frequency marks the maximum significant - * frequency component. - */ - static final int AL_FREQUENCY = 0x2001; - static final int AL_BITS = 0x2002; - static final int AL_CHANNELS = 0x2003; - static final int AL_SIZE = 0x2004; - -/** - * Buffer state. - * - * Not supported for public use (yet). - */ - static final int AL_UNUSED = 0x2010; - static final int AL_PENDING = 0x2011; - static final int AL_PROCESSED = 0x2012; - - -/** Errors: No Error. */ - static final int AL_NO_ERROR = 0; - -/** - * Invalid Name paramater passed to AL call. - */ - static final int AL_INVALID_NAME = 0xA001; - -/** - * Invalid parameter passed to AL call. - */ - static final int AL_INVALID_ENUM = 0xA002; - -/** - * Invalid enum parameter value. - */ - static final int AL_INVALID_VALUE = 0xA003; - -/** - * Illegal call. - */ - static final int AL_INVALID_OPERATION = 0xA004; - - -/** - * No mojo. - */ - static final int AL_OUT_OF_MEMORY = 0xA005; - - -/** Context strings: Vendor Name. */ - static final int AL_VENDOR = 0xB001; - static final int AL_VERSION = 0xB002; - static final int AL_RENDERER = 0xB003; - static final int AL_EXTENSIONS = 0xB004; - -/** Global tweakage. */ - -/** - * Doppler scale. Default 1.0 - */ - static final int AL_DOPPLER_FACTOR = 0xC000; - -/** - * Tweaks speed of propagation. - */ - static final int AL_DOPPLER_VELOCITY = 0xC001; - -/** - * Speed of Sound in units per second - */ - static final int AL_SPEED_OF_SOUND = 0xC003; - -/** - * Distance models - * - * used in conjunction with DistanceModel - * - * implicit: NONE, which disances distance attenuation. - */ - static final int AL_DISTANCE_MODEL = 0xD000; - static final int AL_INVERSE_DISTANCE = 0xD001; - static final int AL_INVERSE_DISTANCE_CLAMPED = 0xD002; - static final int AL_LINEAR_DISTANCE = 0xD003; - static final int AL_LINEAR_DISTANCE_CLAMPED = 0xD004; - static final int AL_EXPONENT_DISTANCE = 0xD005; - static final int AL_EXPONENT_DISTANCE_CLAMPED = 0xD006; - - /* ********** */ - /* FROM efx.h */ - /* ********** */ - - static final String ALC_EXT_EFX_NAME = "ALC_EXT_EFX"; - - static final int ALC_EFX_MAJOR_VERSION = 0x20001; - static final int ALC_EFX_MINOR_VERSION = 0x20002; - static final int ALC_MAX_AUXILIARY_SENDS = 0x20003; - - -///* Listener properties. */ -//#define AL_METERS_PER_UNIT 0x20004 -// -///* Source properties. */ - static final int AL_DIRECT_FILTER = 0x20005; - static final int AL_AUXILIARY_SEND_FILTER = 0x20006; -//#define AL_AIR_ABSORPTION_FACTOR 0x20007 -//#define AL_ROOM_ROLLOFF_FACTOR 0x20008 -//#define AL_CONE_OUTER_GAINHF 0x20009 - static final int AL_DIRECT_FILTER_GAINHF_AUTO = 0x2000A; -//#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B -//#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C -// -// -///* Effect properties. */ -// -///* Reverb effect parameters */ - static final int AL_REVERB_DENSITY = 0x0001; - static final int AL_REVERB_DIFFUSION = 0x0002; - static final int AL_REVERB_GAIN = 0x0003; - static final int AL_REVERB_GAINHF = 0x0004; - static final int AL_REVERB_DECAY_TIME = 0x0005; - static final int AL_REVERB_DECAY_HFRATIO = 0x0006; - static final int AL_REVERB_REFLECTIONS_GAIN = 0x0007; - static final int AL_REVERB_REFLECTIONS_DELAY = 0x0008; - static final int AL_REVERB_LATE_REVERB_GAIN = 0x0009; - static final int AL_REVERB_LATE_REVERB_DELAY = 0x000A; - static final int AL_REVERB_AIR_ABSORPTION_GAINHF = 0x000B; - static final int AL_REVERB_ROOM_ROLLOFF_FACTOR = 0x000C; - static final int AL_REVERB_DECAY_HFLIMIT = 0x000D; - -///* EAX Reverb effect parameters */ -//#define AL_EAXREVERB_DENSITY 0x0001 -//#define AL_EAXREVERB_DIFFUSION 0x0002 -//#define AL_EAXREVERB_GAIN 0x0003 -//#define AL_EAXREVERB_GAINHF 0x0004 -//#define AL_EAXREVERB_GAINLF 0x0005 -//#define AL_EAXREVERB_DECAY_TIME 0x0006 -//#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 -//#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 -//#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 -//#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A -//#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B -//#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C -//#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D -//#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E -//#define AL_EAXREVERB_ECHO_TIME 0x000F -//#define AL_EAXREVERB_ECHO_DEPTH 0x0010 -//#define AL_EAXREVERB_MODULATION_TIME 0x0011 -//#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 -//#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 -//#define AL_EAXREVERB_HFREFERENCE 0x0014 -//#define AL_EAXREVERB_LFREFERENCE 0x0015 -//#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 -//#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 -// -///* Chorus effect parameters */ -//#define AL_CHORUS_WAVEFORM 0x0001 -//#define AL_CHORUS_PHASE 0x0002 -//#define AL_CHORUS_RATE 0x0003 -//#define AL_CHORUS_DEPTH 0x0004 -//#define AL_CHORUS_FEEDBACK 0x0005 -//#define AL_CHORUS_DELAY 0x0006 -// -///* Distortion effect parameters */ -//#define AL_DISTORTION_EDGE 0x0001 -//#define AL_DISTORTION_GAIN 0x0002 -//#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 -//#define AL_DISTORTION_EQCENTER 0x0004 -//#define AL_DISTORTION_EQBANDWIDTH 0x0005 -// -///* Echo effect parameters */ -//#define AL_ECHO_DELAY 0x0001 -//#define AL_ECHO_LRDELAY 0x0002 -//#define AL_ECHO_DAMPING 0x0003 -//#define AL_ECHO_FEEDBACK 0x0004 -//#define AL_ECHO_SPREAD 0x0005 -// -///* Flanger effect parameters */ -//#define AL_FLANGER_WAVEFORM 0x0001 -//#define AL_FLANGER_PHASE 0x0002 -//#define AL_FLANGER_RATE 0x0003 -//#define AL_FLANGER_DEPTH 0x0004 -//#define AL_FLANGER_FEEDBACK 0x0005 -//#define AL_FLANGER_DELAY 0x0006 -// -///* Frequency shifter effect parameters */ -//#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 -//#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 -//#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 -// -///* Vocal morpher effect parameters */ -//#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 -//#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 -//#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 -//#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 -//#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 -//#define AL_VOCAL_MORPHER_RATE 0x0006 -// -///* Pitchshifter effect parameters */ -//#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 -//#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 -// -///* Ringmodulator effect parameters */ -//#define AL_RING_MODULATOR_FREQUENCY 0x0001 -//#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 -//#define AL_RING_MODULATOR_WAVEFORM 0x0003 -// -///* Autowah effect parameters */ -//#define AL_AUTOWAH_ATTACK_TIME 0x0001 -//#define AL_AUTOWAH_RELEASE_TIME 0x0002 -//#define AL_AUTOWAH_RESONANCE 0x0003 -//#define AL_AUTOWAH_PEAK_GAIN 0x0004 -// -///* Compressor effect parameters */ -//#define AL_COMPRESSOR_ONOFF 0x0001 -// -///* Equalizer effect parameters */ -//#define AL_EQUALIZER_LOW_GAIN 0x0001 -//#define AL_EQUALIZER_LOW_CUTOFF 0x0002 -//#define AL_EQUALIZER_MID1_GAIN 0x0003 -//#define AL_EQUALIZER_MID1_CENTER 0x0004 -//#define AL_EQUALIZER_MID1_WIDTH 0x0005 -//#define AL_EQUALIZER_MID2_GAIN 0x0006 -//#define AL_EQUALIZER_MID2_CENTER 0x0007 -//#define AL_EQUALIZER_MID2_WIDTH 0x0008 -//#define AL_EQUALIZER_HIGH_GAIN 0x0009 -//#define AL_EQUALIZER_HIGH_CUTOFF 0x000A -// -///* Effect type */ -//#define AL_EFFECT_FIRST_PARAMETER 0x0000 -//#define AL_EFFECT_LAST_PARAMETER 0x8000 - static final int AL_EFFECT_TYPE = 0x8001; -// -///* Effect types, used with the AL_EFFECT_TYPE property */ -//#define AL_EFFECT_NULL 0x0000 - static final int AL_EFFECT_REVERB = 0x0001; -//#define AL_EFFECT_CHORUS 0x0002 -//#define AL_EFFECT_DISTORTION 0x0003 -//#define AL_EFFECT_ECHO 0x0004 -//#define AL_EFFECT_FLANGER 0x0005 -//#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 -//#define AL_EFFECT_VOCAL_MORPHER 0x0007 -//#define AL_EFFECT_PITCH_SHIFTER 0x0008 -//#define AL_EFFECT_RING_MODULATOR 0x0009 -//#define AL_EFFECT_AUTOWAH 0x000A -//#define AL_EFFECT_COMPRESSOR 0x000B -//#define AL_EFFECT_EQUALIZER 0x000C -//#define AL_EFFECT_EAXREVERB 0x8000 -// -///* Auxiliary Effect Slot properties. */ - static final int AL_EFFECTSLOT_EFFECT = 0x0001; -//#define AL_EFFECTSLOT_GAIN 0x0002 -//#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 -// -///* NULL Auxiliary Slot ID to disable a source send. */ -//#define AL_EFFECTSLOT_NULL 0x0000 -// -// -///* Filter properties. */ -// -///* Lowpass filter parameters */ - static final int AL_LOWPASS_GAIN = 0x0001; - static final int AL_LOWPASS_GAINHF = 0x0002; -// -///* Highpass filter parameters */ -//#define AL_HIGHPASS_GAIN 0x0001 -//#define AL_HIGHPASS_GAINLF 0x0002 -// -///* Bandpass filter parameters */ -//#define AL_BANDPASS_GAIN 0x0001 -//#define AL_BANDPASS_GAINLF 0x0002 -//#define AL_BANDPASS_GAINHF 0x0003 -// -///* Filter type */ -//#define AL_FILTER_FIRST_PARAMETER 0x0000 -//#define AL_FILTER_LAST_PARAMETER 0x8000 - static final int AL_FILTER_TYPE = 0x8001; -// -///* Filter types, used with the AL_FILTER_TYPE property */ - static final int AL_FILTER_NULL = 0x0000; - static final int AL_FILTER_LOWPASS = 0x0001; - static final int AL_FILTER_HIGHPASS = 0x0002; -//#define AL_FILTER_BANDPASS 0x0003 -// -///* Filter ranges and defaults. */ -// -///* Lowpass filter */ -//#define AL_LOWPASS_MIN_GAIN (0.0f) -//#define AL_LOWPASS_MAX_GAIN (1.0f) -//#define AL_LOWPASS_DEFAULT_GAIN (1.0f) -// -//#define AL_LOWPASS_MIN_GAINHF (0.0f) -//#define AL_LOWPASS_MAX_GAINHF (1.0f) -//#define AL_LOWPASS_DEFAULT_GAINHF (1.0f) -// -///* Highpass filter */ -//#define AL_HIGHPASS_MIN_GAIN (0.0f) -//#define AL_HIGHPASS_MAX_GAIN (1.0f) -//#define AL_HIGHPASS_DEFAULT_GAIN (1.0f) -// -//#define AL_HIGHPASS_MIN_GAINLF (0.0f) -//#define AL_HIGHPASS_MAX_GAINLF (1.0f) -//#define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) -// -///* Bandpass filter */ -//#define AL_BANDPASS_MIN_GAIN (0.0f) -//#define AL_BANDPASS_MAX_GAIN (1.0f) -//#define AL_BANDPASS_DEFAULT_GAIN (1.0f) -// -//#define AL_BANDPASS_MIN_GAINHF (0.0f) -//#define AL_BANDPASS_MAX_GAINHF (1.0f) -//#define AL_BANDPASS_DEFAULT_GAINHF (1.0f) -// -//#define AL_BANDPASS_MIN_GAINLF (0.0f) -//#define AL_BANDPASS_MAX_GAINLF (1.0f) -//#define AL_BANDPASS_DEFAULT_GAINLF (1.0f) -// -// -///* Effect parameter ranges and defaults. */ -// -///* Standard reverb effect */ -//#define AL_REVERB_MIN_DENSITY (0.0f) -//#define AL_REVERB_MAX_DENSITY (1.0f) -//#define AL_REVERB_DEFAULT_DENSITY (1.0f) -// -//#define AL_REVERB_MIN_DIFFUSION (0.0f) -//#define AL_REVERB_MAX_DIFFUSION (1.0f) -//#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) -// -//#define AL_REVERB_MIN_GAIN (0.0f) -//#define AL_REVERB_MAX_GAIN (1.0f) -//#define AL_REVERB_DEFAULT_GAIN (0.32f) -// -//#define AL_REVERB_MIN_GAINHF (0.0f) -//#define AL_REVERB_MAX_GAINHF (1.0f) -//#define AL_REVERB_DEFAULT_GAINHF (0.89f) -// -//#define AL_REVERB_MIN_DECAY_TIME (0.1f) -//#define AL_REVERB_MAX_DECAY_TIME (20.0f) -//#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) -// -//#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) -//#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) -//#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) -// -//#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) -//#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) -//#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) -// -//#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) -//#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) -//#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) -// -//#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) -//#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) -//#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) -// -//#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) -//#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) -//#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) -// -//#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) -//#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) -//#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) -// -//#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) -//#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) -//#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) -// -//#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE -//#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE -//#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE -// -///* EAX reverb effect */ -//#define AL_EAXREVERB_MIN_DENSITY (0.0f) -//#define AL_EAXREVERB_MAX_DENSITY (1.0f) -//#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) -// -//#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) -//#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) -//#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) -// -//#define AL_EAXREVERB_MIN_GAIN (0.0f) -//#define AL_EAXREVERB_MAX_GAIN (1.0f) -//#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) -// -//#define AL_EAXREVERB_MIN_GAINHF (0.0f) -//#define AL_EAXREVERB_MAX_GAINHF (1.0f) -//#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) -// -//#define AL_EAXREVERB_MIN_GAINLF (0.0f) -//#define AL_EAXREVERB_MAX_GAINLF (1.0f) -//#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) -// -//#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) -//#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) -//#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) -// -//#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) -//#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) -//#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) -// -//#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) -//#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) -//#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) -// -//#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) -//#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) -//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) -// -//#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) -//#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) -//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) -// -//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) -// -//#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) -//#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) -//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) -// -//#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) -//#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) -//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) -// -//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) -// -//#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) -//#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) -//#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) -// -//#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) -//#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) -//#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) -// -//#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) -//#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) -//#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) -// -//#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) -//#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) -//#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) -// -//#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) -//#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) -//#define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) -// -//#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) -//#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) -//#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) -// -//#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) -//#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) -//#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) -// -//#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) -//#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) -//#define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) -// -//#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE -//#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE -//#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE -// -///* Chorus effect */ -//#define AL_CHORUS_WAVEFORM_SINUSOID (0) -//#define AL_CHORUS_WAVEFORM_TRIANGLE (1) -// -//#define AL_CHORUS_MIN_WAVEFORM (0) -//#define AL_CHORUS_MAX_WAVEFORM (1) -//#define AL_CHORUS_DEFAULT_WAVEFORM (1) -// -//#define AL_CHORUS_MIN_PHASE (-180) -//#define AL_CHORUS_MAX_PHASE (180) -//#define AL_CHORUS_DEFAULT_PHASE (90) -// -//#define AL_CHORUS_MIN_RATE (0.0f) -//#define AL_CHORUS_MAX_RATE (10.0f) -//#define AL_CHORUS_DEFAULT_RATE (1.1f) -// -//#define AL_CHORUS_MIN_DEPTH (0.0f) -//#define AL_CHORUS_MAX_DEPTH (1.0f) -//#define AL_CHORUS_DEFAULT_DEPTH (0.1f) -// -//#define AL_CHORUS_MIN_FEEDBACK (-1.0f) -//#define AL_CHORUS_MAX_FEEDBACK (1.0f) -//#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) -// -//#define AL_CHORUS_MIN_DELAY (0.0f) -//#define AL_CHORUS_MAX_DELAY (0.016f) -//#define AL_CHORUS_DEFAULT_DELAY (0.016f) -// -///* Distortion effect */ -//#define AL_DISTORTION_MIN_EDGE (0.0f) -//#define AL_DISTORTION_MAX_EDGE (1.0f) -//#define AL_DISTORTION_DEFAULT_EDGE (0.2f) -// -//#define AL_DISTORTION_MIN_GAIN (0.01f) -//#define AL_DISTORTION_MAX_GAIN (1.0f) -//#define AL_DISTORTION_DEFAULT_GAIN (0.05f) -// -//#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) -//#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) -//#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) -// -//#define AL_DISTORTION_MIN_EQCENTER (80.0f) -//#define AL_DISTORTION_MAX_EQCENTER (24000.0f) -//#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) -// -//#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) -//#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) -//#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) -// -///* Echo effect */ -//#define AL_ECHO_MIN_DELAY (0.0f) -//#define AL_ECHO_MAX_DELAY (0.207f) -//#define AL_ECHO_DEFAULT_DELAY (0.1f) -// -//#define AL_ECHO_MIN_LRDELAY (0.0f) -//#define AL_ECHO_MAX_LRDELAY (0.404f) -//#define AL_ECHO_DEFAULT_LRDELAY (0.1f) -// -//#define AL_ECHO_MIN_DAMPING (0.0f) -//#define AL_ECHO_MAX_DAMPING (0.99f) -//#define AL_ECHO_DEFAULT_DAMPING (0.5f) -// -//#define AL_ECHO_MIN_FEEDBACK (0.0f) -//#define AL_ECHO_MAX_FEEDBACK (1.0f) -//#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) -// -//#define AL_ECHO_MIN_SPREAD (-1.0f) -//#define AL_ECHO_MAX_SPREAD (1.0f) -//#define AL_ECHO_DEFAULT_SPREAD (-1.0f) -// -///* Flanger effect */ -//#define AL_FLANGER_WAVEFORM_SINUSOID (0) -//#define AL_FLANGER_WAVEFORM_TRIANGLE (1) -// -//#define AL_FLANGER_MIN_WAVEFORM (0) -//#define AL_FLANGER_MAX_WAVEFORM (1) -//#define AL_FLANGER_DEFAULT_WAVEFORM (1) -// -//#define AL_FLANGER_MIN_PHASE (-180) -//#define AL_FLANGER_MAX_PHASE (180) -//#define AL_FLANGER_DEFAULT_PHASE (0) -// -//#define AL_FLANGER_MIN_RATE (0.0f) -//#define AL_FLANGER_MAX_RATE (10.0f) -//#define AL_FLANGER_DEFAULT_RATE (0.27f) -// -//#define AL_FLANGER_MIN_DEPTH (0.0f) -//#define AL_FLANGER_MAX_DEPTH (1.0f) -//#define AL_FLANGER_DEFAULT_DEPTH (1.0f) -// -//#define AL_FLANGER_MIN_FEEDBACK (-1.0f) -//#define AL_FLANGER_MAX_FEEDBACK (1.0f) -//#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) -// -//#define AL_FLANGER_MIN_DELAY (0.0f) -//#define AL_FLANGER_MAX_DELAY (0.004f) -//#define AL_FLANGER_DEFAULT_DELAY (0.002f) -// -///* Frequency shifter effect */ -//#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) -//#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) -//#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) -// -//#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) -//#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) -//#define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) -// -//#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) -//#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) -//#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) -// -//#define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) -//#define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) -//#define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) -// -///* Vocal morpher effect */ -//#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) -//#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) -//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) -// -//#define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) -//#define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) -//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) -// -//#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) -//#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) -//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) -// -//#define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) -//#define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) -//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) -// -//#define AL_VOCAL_MORPHER_PHONEME_A (0) -//#define AL_VOCAL_MORPHER_PHONEME_E (1) -//#define AL_VOCAL_MORPHER_PHONEME_I (2) -//#define AL_VOCAL_MORPHER_PHONEME_O (3) -//#define AL_VOCAL_MORPHER_PHONEME_U (4) -//#define AL_VOCAL_MORPHER_PHONEME_AA (5) -//#define AL_VOCAL_MORPHER_PHONEME_AE (6) -//#define AL_VOCAL_MORPHER_PHONEME_AH (7) -//#define AL_VOCAL_MORPHER_PHONEME_AO (8) -//#define AL_VOCAL_MORPHER_PHONEME_EH (9) -//#define AL_VOCAL_MORPHER_PHONEME_ER (10) -//#define AL_VOCAL_MORPHER_PHONEME_IH (11) -//#define AL_VOCAL_MORPHER_PHONEME_IY (12) -//#define AL_VOCAL_MORPHER_PHONEME_UH (13) -//#define AL_VOCAL_MORPHER_PHONEME_UW (14) -//#define AL_VOCAL_MORPHER_PHONEME_B (15) -//#define AL_VOCAL_MORPHER_PHONEME_D (16) -//#define AL_VOCAL_MORPHER_PHONEME_F (17) -//#define AL_VOCAL_MORPHER_PHONEME_G (18) -//#define AL_VOCAL_MORPHER_PHONEME_J (19) -//#define AL_VOCAL_MORPHER_PHONEME_K (20) -//#define AL_VOCAL_MORPHER_PHONEME_L (21) -//#define AL_VOCAL_MORPHER_PHONEME_M (22) -//#define AL_VOCAL_MORPHER_PHONEME_N (23) -//#define AL_VOCAL_MORPHER_PHONEME_P (24) -//#define AL_VOCAL_MORPHER_PHONEME_R (25) -//#define AL_VOCAL_MORPHER_PHONEME_S (26) -//#define AL_VOCAL_MORPHER_PHONEME_T (27) -//#define AL_VOCAL_MORPHER_PHONEME_V (28) -//#define AL_VOCAL_MORPHER_PHONEME_Z (29) -// -//#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) -//#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) -//#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) -// -//#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) -//#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) -//#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) -// -//#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) -//#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) -//#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) -// -///* Pitch shifter effect */ -//#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) -//#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) -//#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) -// -//#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) -//#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) -//#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) -// -///* Ring modulator effect */ -//#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) -//#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) -//#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) -// -//#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) -//#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) -//#define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) -// -//#define AL_RING_MODULATOR_SINUSOID (0) -//#define AL_RING_MODULATOR_SAWTOOTH (1) -//#define AL_RING_MODULATOR_SQUARE (2) -// -//#define AL_RING_MODULATOR_MIN_WAVEFORM (0) -//#define AL_RING_MODULATOR_MAX_WAVEFORM (2) -//#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) -// -///* Autowah effect */ -//#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) -//#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) -//#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) -// -//#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) -//#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) -//#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) -// -//#define AL_AUTOWAH_MIN_RESONANCE (2.0f) -//#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) -//#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) -// -//#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) -//#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) -//#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) -// -///* Compressor effect */ -//#define AL_COMPRESSOR_MIN_ONOFF (0) -//#define AL_COMPRESSOR_MAX_ONOFF (1) -//#define AL_COMPRESSOR_DEFAULT_ONOFF (1) -// -///* Equalizer effect */ -//#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) -//#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) -//#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) -// -//#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) -//#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) -//#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) -// -//#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) -//#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) -//#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) -// -//#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) -//#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) -//#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) -// -//#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) -//#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) -//#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) -// -//#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) -//#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) -//#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) -// -//#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) -//#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) -//#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) -// -//#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) -//#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) -//#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) -// -//#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) -//#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) -//#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) -// -//#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) -//#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) -//#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) -// -// -///* Source parameter value ranges and defaults. */ -//#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) -//#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) -//#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) -// -//#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) -//#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) -//#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) -// -//#define AL_MIN_CONE_OUTER_GAINHF (0.0f) -//#define AL_MAX_CONE_OUTER_GAINHF (1.0f) -//#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) -// -//#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE -//#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE -//#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE -// -//#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE -//#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE -//#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE -// -//#define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE -//#define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE -//#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE -// -// -///* Listener parameter value ranges and defaults. */ -//#define AL_MIN_METERS_PER_UNIT FLT_MIN -//#define AL_MAX_METERS_PER_UNIT FLT_MAX -//#define AL_DEFAULT_METERS_PER_UNIT (1.0f) - - - public static String GetALErrorMsg(int errorCode) { - String errorText; - switch (errorCode) { - case AL_NO_ERROR: - errorText = "No Error"; - break; - case AL_INVALID_NAME: - errorText = "Invalid Name"; - break; - case AL_INVALID_ENUM: - errorText = "Invalid Enum"; - break; - case AL_INVALID_VALUE: - errorText = "Invalid Value"; - break; - case AL_INVALID_OPERATION: - errorText = "Invalid Operation"; - break; - case AL_OUT_OF_MEMORY: - errorText = "Out of Memory"; - break; - default: - errorText = "Unknown Error Code: " + String.valueOf(errorCode); - } - return errorText; - } -} - diff --git a/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java b/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java deleted file mode 100644 index e7f4a0f98..000000000 --- a/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java +++ /dev/null @@ -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); - } -} diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java new file mode 100644 index 000000000..7812dc748 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java @@ -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); + +} diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java new file mode 100644 index 000000000..f1579c9e5 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java @@ -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(); +} diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java new file mode 100644 index 000000000..d7a569c1f --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java @@ -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); +} diff --git a/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java b/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java deleted file mode 100644 index a11425b23..000000000 --- a/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java +++ /dev/null @@ -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; - -/** - * AndroidAudioLoader 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; - } -} diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java deleted file mode 100644 index 8a59d0be8..000000000 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java +++ /dev/null @@ -1,2573 +0,0 @@ -package com.jme3.renderer.ios; - -import com.jme3.light.LightList; -import com.jme3.material.RenderState; -import com.jme3.math.*; -import com.jme3.renderer.*; -import com.jme3.scene.Mesh; -import com.jme3.scene.Mesh.Mode; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.shader.Attribute; -import com.jme3.shader.Shader; -import com.jme3.shader.Shader.ShaderSource; -import com.jme3.shader.Shader.ShaderType; -import com.jme3.shader.Uniform; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.FrameBuffer.RenderBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.WrapAxis; -import com.jme3.util.BufferUtils; -import com.jme3.util.ListMap; -import com.jme3.util.NativeObjectManager; - -import java.nio.*; -import java.util.EnumSet; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3tools.shader.ShaderDebug; - -/** - * The Renderer is responsible for taking rendering commands and - * executing them on the underlying video hardware. - * - * @author Kirill Vainer - */ -public class IGLESShaderRenderer implements Renderer { - - private static final Logger logger = Logger.getLogger(IGLESShaderRenderer.class.getName()); - private static final boolean VALIDATE_SHADER = false; - - private final NativeObjectManager objManager = new NativeObjectManager(); - private final EnumSet caps = EnumSet.noneOf(Caps.class); - private final Statistics statistics = new Statistics(); - private final StringBuilder stringBuf = new StringBuilder(250); - private final RenderContext context = new RenderContext(); - private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); - - private final int maxFBOAttachs = 1; // Only 1 color attachment on ES - private final int maxMRTFBOAttachs = 1; // FIXME for now, not sure if > 1 is needed for ES - - private final int[] intBuf1 = new int[1]; - private final int[] intBuf16 = new int[16]; - - private int glslVer; - private int vertexTextureUnits; - private int fragTextureUnits; - private int vertexUniforms; - private int fragUniforms; - private int vertexAttribs; - private int maxRBSize; - private int maxTexSize; - private int maxCubeTexSize; - - private FrameBuffer lastFb = null; - private FrameBuffer mainFbOverride = null; - private boolean useVBO = true; - private boolean powerVr = false; - private boolean uintIndexSupport = false; - - private Shader boundShader; - - private int vpX, vpY, vpW, vpH; - private int clipX, clipY, clipW, clipH; - - public IGLESShaderRenderer() { - logger.log(Level.FINE, "IGLESShaderRenderer Constructor"); - } - - /** - * Get the capabilities of the renderer. - * @return The capabilities of the renderer. - */ - public EnumSet getCaps() { - logger.log(Level.FINE, "IGLESShaderRenderer getCaps"); - return caps; - } - - /** - * The statistics allow tracking of how data - * per frame, such as number of objects rendered, number of triangles, etc. - * These are updated when the Renderer's methods are used, make sure - * to call {@link Statistics#clearFrame() } at the appropriate time - * to get accurate info per frame. - */ - public Statistics getStatistics() { - logger.log(Level.FINE, "IGLESShaderRenderer getStatistics"); - return statistics; - } - - /** - * Invalidates the current rendering state. Should be called after - * the GL state was changed manually or through an external library. - */ - public void invalidateState() { - logger.log(Level.FINE, "IGLESShaderRenderer invalidateState"); - } - - /** - * Clears certain channels of the currently bound framebuffer. - * - * @param color True if to clear colors (RGBA) - * @param depth True if to clear depth/z - * @param stencil True if to clear stencil buffer (if available, otherwise - * ignored) - */ - public void clearBuffers(boolean color, boolean depth, boolean stencil) { - logger.log(Level.FINE, "IGLESShaderRenderer clearBuffers"); - int bits = 0; - if (color) { - //See explanations of the depth below, we must enable color write to be able to clear the color buffer - if (context.colorWriteEnabled == false) { - JmeIosGLES.glColorMask(true, true, true, true); - context.colorWriteEnabled = true; - } - bits = JmeIosGLES.GL_COLOR_BUFFER_BIT; - } - if (depth) { - //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false - //here s some link on openl board - //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223 - //if depth clear is requested, we enable the depthMask - if (context.depthWriteEnabled == false) { - JmeIosGLES.glDepthMask(true); - context.depthWriteEnabled = true; - } - bits |= JmeIosGLES.GL_DEPTH_BUFFER_BIT; - } - if (stencil) { - bits |= JmeIosGLES.GL_STENCIL_BUFFER_BIT; - } - if (bits != 0) { - JmeIosGLES.glClear(bits); - JmeIosGLES.checkGLError(); - } - } - - /** - * Sets the background (aka clear) color. - * - * @param color The background color to set - */ - public void setBackgroundColor(ColorRGBA color) { - logger.log(Level.FINE, "IGLESShaderRenderer setBackgroundColor"); - JmeIosGLES.glClearColor(color.r, color.g, color.b, color.a); - JmeIosGLES.checkGLError(); - } - - /** - * Applies the given {@link RenderState}, making the necessary - * GL calls so that the state is applied. - */ - public void applyRenderState(RenderState state) { - logger.log(Level.FINE, "IGLESShaderRenderer applyRenderState"); - /* - if (state.isWireframe() && !context.wireframe){ - GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_LINE); - context.wireframe = true; - }else if (!state.isWireframe() && context.wireframe){ - GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_FILL); - context.wireframe = false; - } - */ - if (state.isDepthTest() && !context.depthTestEnabled) { - JmeIosGLES.glEnable(JmeIosGLES.GL_DEPTH_TEST); - JmeIosGLES.glDepthFunc(convertTestFunction(context.depthFunc)); - JmeIosGLES.checkGLError(); - context.depthTestEnabled = true; - } else if (!state.isDepthTest() && context.depthTestEnabled) { - JmeIosGLES.glDisable(JmeIosGLES.GL_DEPTH_TEST); - JmeIosGLES.checkGLError(); - context.depthTestEnabled = false; - } - if (state.getDepthFunc() != context.depthFunc) { - JmeIosGLES.glDepthFunc(convertTestFunction(state.getDepthFunc())); - context.depthFunc = state.getDepthFunc(); - } - - if (state.isDepthWrite() && !context.depthWriteEnabled) { - JmeIosGLES.glDepthMask(true); - JmeIosGLES.checkGLError(); - context.depthWriteEnabled = true; - } else if (!state.isDepthWrite() && context.depthWriteEnabled) { - JmeIosGLES.glDepthMask(false); - JmeIosGLES.checkGLError(); - context.depthWriteEnabled = false; - } - if (state.isColorWrite() && !context.colorWriteEnabled) { - JmeIosGLES.glColorMask(true, true, true, true); - JmeIosGLES.checkGLError(); - context.colorWriteEnabled = true; - } else if (!state.isColorWrite() && context.colorWriteEnabled) { - JmeIosGLES.glColorMask(false, false, false, false); - JmeIosGLES.checkGLError(); - context.colorWriteEnabled = false; - } -// if (state.isPointSprite() && !context.pointSprite) { -//// GLES20.glEnable(GLES20.GL_POINT_SPRITE); -//// GLES20.glTexEnvi(GLES20.GL_POINT_SPRITE, GLES20.GL_COORD_REPLACE, GLES20.GL_TRUE); -//// GLES20.glEnable(GLES20.GL_VERTEX_PROGRAM_POINT_SIZE); -//// GLES20.glPointParameterf(GLES20.GL_POINT_SIZE_MIN, 1.0f); -// } else if (!state.isPointSprite() && context.pointSprite) { -//// GLES20.glDisable(GLES20.GL_POINT_SPRITE); -// } - - if (state.isPolyOffset()) { - if (!context.polyOffsetEnabled) { - JmeIosGLES.glEnable(JmeIosGLES.GL_POLYGON_OFFSET_FILL); - JmeIosGLES.glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - JmeIosGLES.checkGLError(); - - context.polyOffsetEnabled = true; - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } else { - if (state.getPolyOffsetFactor() != context.polyOffsetFactor - || state.getPolyOffsetUnits() != context.polyOffsetUnits) { - JmeIosGLES.glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - JmeIosGLES.checkGLError(); - - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } - } - } else { - if (context.polyOffsetEnabled) { - JmeIosGLES.glDisable(JmeIosGLES.GL_POLYGON_OFFSET_FILL); - JmeIosGLES.checkGLError(); - - context.polyOffsetEnabled = false; - context.polyOffsetFactor = 0; - context.polyOffsetUnits = 0; - } - } - if (state.getFaceCullMode() != context.cullMode) { - if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { - JmeIosGLES.glDisable(JmeIosGLES.GL_CULL_FACE); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glEnable(JmeIosGLES.GL_CULL_FACE); - JmeIosGLES.checkGLError(); - } - - switch (state.getFaceCullMode()) { - case Off: - break; - case Back: - JmeIosGLES.glCullFace(JmeIosGLES.GL_BACK); - JmeIosGLES.checkGLError(); - break; - case Front: - JmeIosGLES.glCullFace(JmeIosGLES.GL_FRONT); - JmeIosGLES.checkGLError(); - break; - case FrontAndBack: - JmeIosGLES.glCullFace(JmeIosGLES.GL_FRONT_AND_BACK); - JmeIosGLES.checkGLError(); - break; - default: - throw new UnsupportedOperationException("Unrecognized face cull mode: " - + state.getFaceCullMode()); - } - - context.cullMode = state.getFaceCullMode(); - } - - if (state.getBlendMode() != context.blendMode) { - if (state.getBlendMode() == RenderState.BlendMode.Off) { - JmeIosGLES.glDisable(JmeIosGLES.GL_BLEND); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glEnable(JmeIosGLES.GL_BLEND); - switch (state.getBlendMode()) { - case Off: - break; - case Additive: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE); - break; - case AlphaAdditive: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_SRC_ALPHA, JmeIosGLES.GL_ONE); - break; - case Color: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE_MINUS_SRC_COLOR); - break; - case Alpha: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_SRC_ALPHA, JmeIosGLES.GL_ONE_MINUS_SRC_ALPHA); - break; - case PremultAlpha: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE_MINUS_SRC_ALPHA); - break; - case Modulate: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_DST_COLOR, JmeIosGLES.GL_ZERO); - break; - case ModulateX2: - JmeIosGLES.glBlendFunc(JmeIosGLES.GL_DST_COLOR, JmeIosGLES.GL_SRC_COLOR); - break; - default: - throw new UnsupportedOperationException("Unrecognized blend mode: " - + state.getBlendMode()); - } - JmeIosGLES.checkGLError(); - } - context.blendMode = state.getBlendMode(); - } - } - - /** - * Set the range of the depth values for objects. All rendered - * objects will have their depth clamped to this range. - * - * @param start The range start - * @param end The range end - */ - public void setDepthRange(float start, float end) { - logger.log(Level.FINE, "IGLESShaderRenderer setDepthRange"); - JmeIosGLES.glDepthRangef(start, end); - JmeIosGLES.checkGLError(); - } - - /** - * Called when a new frame has been rendered. - */ - public void postFrame() { - logger.log(Level.FINE, "IGLESShaderRenderer onFrame"); - //JmeIosGLES.checkGLErrorForced(); - JmeIosGLES.checkGLError(); - - objManager.deleteUnused(this); - } - - /** - * Set the world matrix to use. Does nothing if the Renderer is - * shader based. - * - * @param worldMatrix World matrix to use. - */ - public void setWorldMatrix(Matrix4f worldMatrix) { - logger.log(Level.FINE, "IGLESShaderRenderer setWorldMatrix"); - } - - /** - * Sets the view and projection matrices to use. Does nothing if the Renderer - * is shader based. - * - * @param viewMatrix The view matrix to use. - * @param projMatrix The projection matrix to use. - */ - public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { - logger.log(Level.FINE, "IGLESShaderRenderer setViewProjectionMatrices"); - } - - /** - * Set the viewport location and resolution on the screen. - * - * @param x The x coordinate of the viewport - * @param y The y coordinate of the viewport - * @param width Width of the viewport - * @param height Height of the viewport - */ - public void setViewPort(int x, int y, int width, int height) { - logger.log(Level.FINE, "IGLESShaderRenderer setViewPort"); - if (x != vpX || vpY != y || vpW != width || vpH != height) { - JmeIosGLES.glViewport(x, y, width, height); - JmeIosGLES.checkGLError(); - - vpX = x; - vpY = y; - vpW = width; - vpH = height; - } - } - - /** - * Specifies a clipping rectangle. - * For all future rendering commands, no pixels will be allowed - * to be rendered outside of the clip rectangle. - * - * @param x The x coordinate of the clip rect - * @param y The y coordinate of the clip rect - * @param width Width of the clip rect - * @param height Height of the clip rect - */ - public void setClipRect(int x, int y, int width, int height) { - logger.log(Level.FINE, "IGLESShaderRenderer setClipRect"); - if (!context.clipRectEnabled) { - JmeIosGLES.glEnable(JmeIosGLES.GL_SCISSOR_TEST); - JmeIosGLES.checkGLError(); - context.clipRectEnabled = true; - } - if (clipX != x || clipY != y || clipW != width || clipH != height) { - JmeIosGLES.glScissor(x, y, width, height); - JmeIosGLES.checkGLError(); - clipX = x; - clipY = y; - clipW = width; - clipH = height; - } - } - - /** - * Clears the clipping rectangle set with - * {@link #setClipRect(int, int, int, int) }. - */ - public void clearClipRect() { - logger.log(Level.FINE, "IGLESShaderRenderer clearClipRect"); - if (context.clipRectEnabled) { - JmeIosGLES.glDisable(JmeIosGLES.GL_SCISSOR_TEST); - JmeIosGLES.checkGLError(); - context.clipRectEnabled = false; - - clipX = 0; - clipY = 0; - clipW = 0; - clipH = 0; - } - } - - /** - * Set lighting state. - * Does nothing if the renderer is shader based. - * The lights should be provided in world space. - * Specify null to disable lighting. - * - * @param lights The light list to set. - */ - public void setLighting(LightList lights) { - logger.log(Level.FINE, "IGLESShaderRenderer setLighting"); - } - - /** - * Sets the shader to use for rendering. - * If the shader has not been uploaded yet, it is compiled - * and linked. If it has been uploaded, then the - * uniform data is updated and the shader is set. - * - * @param shader The shader to use for rendering. - */ - public void setShader(Shader shader) { - logger.log(Level.FINE, "IGLESShaderRenderer setShader"); - if (shader == null) { - throw new IllegalArgumentException("Shader cannot be null"); - } else { - if (shader.isUpdateNeeded()) { - updateShaderData(shader); - } - - // NOTE: might want to check if any of the - // sources need an update? - - assert shader.getId() > 0; - - updateShaderUniforms(shader); - bindProgram(shader); - } - } - - /** - * Deletes a shader. This method also deletes - * the attached shader sources. - * - * @param shader Shader to delete. - */ - public void deleteShader(Shader shader) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteShader"); - if (shader.getId() == -1) { - logger.warning("Shader is not uploaded to GPU, cannot delete."); - return; - } - - for (ShaderSource source : shader.getSources()) { - if (source.getId() != -1) { - JmeIosGLES.glDetachShader(shader.getId(), source.getId()); - JmeIosGLES.checkGLError(); - - deleteShaderSource(source); - } - } - - JmeIosGLES.glDeleteProgram(shader.getId()); - JmeIosGLES.checkGLError(); - - statistics.onDeleteShader(); - shader.resetObject(); - } - - /** - * Deletes the provided shader source. - * - * @param source The ShaderSource to delete. - */ - public void deleteShaderSource(ShaderSource source) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteShaderSource"); - if (source.getId() < 0) { - logger.warning("Shader source is not uploaded to GPU, cannot delete."); - return; - } - - source.clearUpdateNeeded(); - - JmeIosGLES.glDeleteShader(source.getId()); - JmeIosGLES.checkGLError(); - - source.resetObject(); - } - - /** - * Copies contents from src to dst, scaling if necessary. - */ - public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { - logger.log(Level.FINE, "IGLESShaderRenderer copyFrameBuffer"); - copyFrameBuffer(src, dst, true); - } - - /** - * Copies contents from src to dst, scaling if necessary. - * set copyDepth to false to only copy the color buffers. - */ - public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { - logger.warning("IGLESShaderRenderer copyFrameBuffer with depth TODO"); - throw new RendererException("Copy framebuffer not implemented yet."); - } - - /** - * Sets the framebuffer that will be drawn to. - */ - public void setFrameBuffer(FrameBuffer fb) { - logger.log(Level.FINE, "IGLESShaderRenderer setFrameBuffer"); - if (fb == null && mainFbOverride != null) { - fb = mainFbOverride; - } - - if (lastFb == fb) { - if (fb == null || !fb.isUpdateNeeded()) { - return; - } - } - - // generate mipmaps for last FB if needed - if (lastFb != null) { - for (int i = 0; i < lastFb.getNumColorBuffers(); i++) { - RenderBuffer rb = lastFb.getColorBuffer(i); - Texture tex = rb.getTexture(); - if (tex != null - && tex.getMinFilter().usesMipMapLevels()) { - setTexture(0, rb.getTexture()); - -// int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); - int textureType = convertTextureType(tex.getType()); - JmeIosGLES.glGenerateMipmap(textureType); - JmeIosGLES.checkGLError(); - } - } - } - - if (fb == null) { - // unbind any fbos - if (context.boundFBO != 0) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, 0); - JmeIosGLES.checkGLError(); - - statistics.onFrameBufferUse(null, true); - - context.boundFBO = 0; - } - - /* - // select back buffer - if (context.boundDrawBuf != -1) { - glDrawBuffer(initialDrawBuf); - context.boundDrawBuf = -1; - } - if (context.boundReadBuf != -1) { - glReadBuffer(initialReadBuf); - context.boundReadBuf = -1; - } - */ - - lastFb = null; - } else { - if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { - throw new IllegalArgumentException("The framebuffer: " + fb - + "\nDoesn't have any color/depth buffers"); - } - - if (fb.isUpdateNeeded()) { - updateFrameBuffer(fb); - } - - if (context.boundFBO != fb.getId()) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, fb.getId()); - JmeIosGLES.checkGLError(); - - statistics.onFrameBufferUse(fb, true); - - // update viewport to reflect framebuffer's resolution - setViewPort(0, 0, fb.getWidth(), fb.getHeight()); - - context.boundFBO = fb.getId(); - } else { - statistics.onFrameBufferUse(fb, false); - } - if (fb.getNumColorBuffers() == 0) { -// // make sure to select NONE as draw buf -// // no color buffer attached. select NONE - if (context.boundDrawBuf != -2) { -// glDrawBuffer(GL_NONE); - context.boundDrawBuf = -2; - } - if (context.boundReadBuf != -2) { -// glReadBuffer(GL_NONE); - context.boundReadBuf = -2; - } - } else { - if (fb.getNumColorBuffers() > maxFBOAttachs) { - throw new RendererException("Framebuffer has more color " - + "attachments than are supported" - + " by the video hardware!"); - } - if (fb.isMultiTarget()) { - if (fb.getNumColorBuffers() > maxMRTFBOAttachs) { - throw new RendererException("Framebuffer has more" - + " multi targets than are supported" - + " by the video hardware!"); - } - - if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) { - //intBuf16.clear(); - for (int i = 0; i < 16; i++) { - intBuf16[i] = i < fb.getNumColorBuffers() ? JmeIosGLES.GL_COLOR_ATTACHMENT0 + i : 0; - } - - //intBuf16.flip();// TODO: flip -// glDrawBuffers(intBuf16); - context.boundDrawBuf = 100 + fb.getNumColorBuffers(); - } - } else { - RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); - // select this draw buffer - if (context.boundDrawBuf != rb.getSlot()) { - JmeIosGLES.glActiveTexture(convertAttachmentSlot(rb.getSlot())); - JmeIosGLES.checkGLError(); - - context.boundDrawBuf = rb.getSlot(); - } - } - } - - assert fb.getId() >= 0; - assert context.boundFBO == fb.getId(); - - lastFb = fb; - - checkFrameBufferStatus(fb); - } - } - - /** - * Set the framebuffer that will be set instead of the main framebuffer - * when a call to setFrameBuffer(null) is made. - * - * @param fb - */ - public void setMainFrameBufferOverride(FrameBuffer fb) { - logger.log(Level.FINE, "IGLESShaderRenderer setMainFrameBufferOverride"); - mainFbOverride = fb; - } - - /** - * Reads the pixels currently stored in the specified framebuffer - * into the given ByteBuffer object. - * Only color pixels are transferred, the format is BGRA with 8 bits - * per component. The given byte buffer should have at least - * fb.getWidth() * fb.getHeight() * 4 bytes remaining. - * - * @param fb The framebuffer to read from - * @param byteBuf The bytebuffer to transfer color data to - */ - public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { - logger.log(Level.FINE, "IGLESShaderRenderer readFrameBuffer"); - if (fb != null) { - RenderBuffer rb = fb.getColorBuffer(); - if (rb == null) { - throw new IllegalArgumentException("Specified framebuffer" - + " does not have a colorbuffer"); - } - - setFrameBuffer(fb); - } else { - setFrameBuffer(null); - } - - //JmeIosGLES.glReadPixels2(vpX, vpY, vpW, vpH, JmeIosGLES.GL_RGBA, JmeIosGLES.GL_UNSIGNED_BYTE, byteBuf.array(), 0, vpW * vpH * 4); - JmeIosGLES.glReadPixels(vpX, vpY, vpW, vpH, JmeIosGLES.GL_RGBA, JmeIosGLES.GL_UNSIGNED_BYTE, byteBuf); - JmeIosGLES.checkGLError(); - } - - /** - * Deletes a framebuffer and all attached renderbuffers - */ - public void deleteFrameBuffer(FrameBuffer fb) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteFrameBuffer"); - if (fb.getId() != -1) { - if (context.boundFBO == fb.getId()) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, 0); - JmeIosGLES.checkGLError(); - - context.boundFBO = 0; - } - - if (fb.getDepthBuffer() != null) { - deleteRenderBuffer(fb, fb.getDepthBuffer()); - } - if (fb.getColorBuffer() != null) { - deleteRenderBuffer(fb, fb.getColorBuffer()); - } - - intBuf1[0] = fb.getId(); - JmeIosGLES.glDeleteFramebuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - fb.resetObject(); - - statistics.onDeleteFrameBuffer(); - } - } - - /** - * Sets the texture to use for the given texture unit. - */ - public void setTexture(int unit, Texture tex) { - logger.log(Level.FINE, "IGLESShaderRenderer setTexture"); - Image image = tex.getImage(); - if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated()) ) { - updateTexImageData(image, tex.getType()); - } - - int texId = image.getId(); - assert texId != -1; - - if (texId == -1) { - logger.warning("error: texture image has -1 id"); - } - - Image[] textures = context.boundTextures; - - int type = convertTextureType(tex.getType()); - if (textures[unit] != image) { - if (context.boundTextureUnit != unit) { - JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0 + unit); - context.boundTextureUnit = unit; - } - - JmeIosGLES.glBindTexture(type, texId); - JmeIosGLES.checkGLError(); - - textures[unit] = image; - - statistics.onTextureUse(tex.getImage(), true); - } else { - statistics.onTextureUse(tex.getImage(), false); - } - - setupTextureParams(tex); - } - - /** - * Modify the given Texture tex with the given Image. The image will be put at x and y into the texture. - * - * @param tex the Texture that will be modified - * @param pixels the source Image data to copy data from - * @param x the x position to put the image into the texture - * @param y the y position to put the image into the texture - */ - public void modifyTexture(Texture tex, Image pixels, int x, int y) { - logger.log(Level.FINE, "IGLESShaderRenderer modifyTexture"); - setTexture(0, tex); - TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); - } - - /** - * Deletes a texture from the GPU. - */ - public void deleteImage(Image image) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteImage"); - int texId = image.getId(); - if (texId != -1) { - intBuf1[0] = texId; - - JmeIosGLES.glDeleteTextures(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - image.resetObject(); - - statistics.onDeleteTexture(); - } - } - - /** - * Uploads a vertex buffer to the GPU. - * - * @param vb The vertex buffer to upload - */ - public void updateBufferData(VertexBuffer vb) { - logger.log(Level.FINE, "IGLESShaderRenderer updateBufferData"); - int bufId = vb.getId(); - boolean created = false; - if (bufId == -1) { - // create buffer - JmeIosGLES.glGenBuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - bufId = intBuf1[0]; - vb.setId(bufId); - objManager.registerObject(vb); - - created = true; - } - - // bind buffer - int target; - if (vb.getBufferType() == VertexBuffer.Type.Index) { - target = JmeIosGLES.GL_ELEMENT_ARRAY_BUFFER; - if (context.boundElementArrayVBO != bufId) { - JmeIosGLES.glBindBuffer(target, bufId); - JmeIosGLES.checkGLError(); - - context.boundElementArrayVBO = bufId; - } - } else { - target = JmeIosGLES.GL_ARRAY_BUFFER; - if (context.boundArrayVBO != bufId) { - JmeIosGLES.glBindBuffer(target, bufId); - JmeIosGLES.checkGLError(); - - context.boundArrayVBO = bufId; - } - } - - int usage = convertUsage(vb.getUsage()); - vb.getData().rewind(); - - if (created || vb.hasDataSizeChanged()) { - // upload data based on format - int size = vb.getData().limit() * vb.getFormat().getComponentSize(); - - switch (vb.getFormat()) { - case Byte: - case UnsignedByte: - JmeIosGLES.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - case Short: - case UnsignedShort: - JmeIosGLES.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - case Int: - case UnsignedInt: - JmeIosGLES.glBufferData(target, size, (IntBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - case Float: - JmeIosGLES.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); - JmeIosGLES.checkGLError(); - break; - default: - throw new RuntimeException("Unknown buffer format."); - } - } else { - int size = vb.getData().limit() * vb.getFormat().getComponentSize(); - - switch (vb.getFormat()) { - case Byte: - case UnsignedByte: - JmeIosGLES.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - case Short: - case UnsignedShort: - JmeIosGLES.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - case Int: - case UnsignedInt: - JmeIosGLES.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - case Float: - JmeIosGLES.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); - JmeIosGLES.checkGLError(); - break; - default: - throw new RuntimeException("Unknown buffer format."); - } - } - vb.clearUpdateNeeded(); - } - - /** - * Deletes a vertex buffer from the GPU. - * @param vb The vertex buffer to delete - */ - public void deleteBuffer(VertexBuffer vb) { - logger.log(Level.FINE, "IGLESShaderRenderer deleteBuffer"); - int bufId = vb.getId(); - if (bufId != -1) { - // delete buffer - intBuf1[0] = bufId; - - JmeIosGLES.glDeleteBuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - vb.resetObject(); - } - } - - /** - * Renders count meshes, with the geometry data supplied. - * The shader which is currently set with setShader is - * responsible for transforming the input verticies into clip space - * and shading it based on the given vertex attributes. - * The int variable gl_InstanceID can be used to access the current - * instance of the mesh being rendered inside the vertex shader. - * - * @param mesh The mesh to render - * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. - * @param count Number of mesh instances to render - */ - public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - logger.log(Level.FINE, "IGLESShaderRenderer renderMesh"); - if (mesh.getVertexCount() == 0) { - return; - } - /* - * NOTE: not supported in OpenGL ES 2.0. - if (context.pointSize != mesh.getPointSize()) { - GLES10.glPointSize(mesh.getPointSize()); - context.pointSize = mesh.getPointSize(); - } - */ - if (context.lineWidth != mesh.getLineWidth()) { - JmeIosGLES.glLineWidth(mesh.getLineWidth()); - JmeIosGLES.checkGLError(); - context.lineWidth = mesh.getLineWidth(); - } - - statistics.onMeshDrawn(mesh, lod); -// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ -// renderMeshVertexArray(mesh, lod, count); -// }else{ - - if (useVBO) { - renderMeshDefault(mesh, lod, count); - } else { - renderMeshVertexArray(mesh, lod, count); - } - } - - /** - * Resets all previously used {@link NativeObject Native Objects} on this Renderer. - * The state of the native objects is reset in such way, that using - * them again will cause the renderer to reupload them. - * Call this method when you know the GL context is going to shutdown. - * - * @see NativeObject#resetObject() - */ - public void resetGLObjects() { - logger.log(Level.FINE, "IGLESShaderRenderer resetGLObjects"); - objManager.resetObjects(); - statistics.clearMemory(); - boundShader = null; - lastFb = null; - context.reset(); - } - - /** - * Deletes all previously used {@link NativeObject Native Objects} on this Renderer, and - * then resets the native objects. - * - * @see #resetGLObjects() - * @see NativeObject#deleteObject(java.lang.Object) - */ - public void cleanup() { - logger.log(Level.FINE, "IGLESShaderRenderer cleanup"); - objManager.deleteAllObjects(this); - statistics.clearMemory(); - } - - /** - * Sets the alpha to coverage state. - *

- * When alpha coverage and multi-sampling is enabled, - * each pixel will contain alpha coverage in all - * of its subsamples, which is then combined when - * other future alpha-blended objects are rendered. - *

- *

- * Alpha-to-coverage is useful for rendering transparent objects - * without having to worry about sorting them. - *

- */ - public void setAlphaToCoverage(boolean value) { - logger.log(Level.FINE, "IGLESShaderRenderer setAlphaToCoverage"); - if (value) { - JmeIosGLES.glEnable(JmeIosGLES.GL_SAMPLE_ALPHA_TO_COVERAGE); - JmeIosGLES.checkGLError(); - } else { - JmeIosGLES.glDisable(JmeIosGLES.GL_SAMPLE_ALPHA_TO_COVERAGE); - JmeIosGLES.checkGLError(); - } - } - - - /* ------------------------------------------------------------------------------ */ - - - public void initialize() { - Level store = logger.getLevel(); - logger.setLevel(Level.FINE); - - logger.log(Level.FINE, "Vendor: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_VENDOR)); - logger.log(Level.FINE, "Renderer: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_RENDERER)); - logger.log(Level.FINE, "Version: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_VERSION)); - logger.log(Level.FINE, "Shading Language Version: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_SHADING_LANGUAGE_VERSION)); - - /* - // Fix issue in TestRenderToMemory when GL_FRONT is the main - // buffer being used. - initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); - initialReadBuf = glGetInteger(GL_READ_BUFFER); - - // XXX: This has to be GL_BACK for canvas on Mac - // Since initialDrawBuf is GL_FRONT for pbuffer, gotta - // change this value later on ... -// initialDrawBuf = GL_BACK; -// initialReadBuf = GL_BACK; - */ - - // Check OpenGL version - int openGlVer = extractVersion("OpenGL ES ", JmeIosGLES.glGetString(JmeIosGLES.GL_VERSION)); - if (openGlVer == -1) { - glslVer = -1; - throw new UnsupportedOperationException("OpenGL ES 2.0+ is required for IGLESShaderRenderer!"); - } - - // Check shader language version - glslVer = extractVersion("OpenGL ES GLSL ES ", JmeIosGLES.glGetString(JmeIosGLES.GL_SHADING_LANGUAGE_VERSION)); - switch (glslVer) { - // TODO: When new versions of OpenGL ES shader language come out, - // update this. - default: - caps.add(Caps.GLSL100); - break; - } - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16, 0); - vertexTextureUnits = intBuf16[0]; - logger.log(Level.FINE, "VTF Units: {0}", vertexTextureUnits); - if (vertexTextureUnits > 0) { - caps.add(Caps.VertexTextureFetch); - } - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16, 0); - fragTextureUnits = intBuf16[0]; - logger.log(Level.FINE, "Texture Units: {0}", fragTextureUnits); - - // Multiply vector count by 4 to get float count. - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_UNIFORM_VECTORS, intBuf16, 0); - vertexUniforms = intBuf16[0] * 4; - logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_FRAGMENT_UNIFORM_VECTORS, intBuf16, 0); - fragUniforms = intBuf16[0] * 4; - logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VARYING_VECTORS, intBuf16, 0); - int varyingFloats = intBuf16[0] * 4; - logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_ATTRIBS, intBuf16, 0); - vertexAttribs = intBuf16[0]; - logger.log(Level.FINE, "Vertex Attributes: {0}", vertexAttribs); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_SUBPIXEL_BITS, intBuf16, 0); - int subpixelBits = intBuf16[0]; - logger.log(Level.FINE, "Subpixel Bits: {0}", subpixelBits); - -// GLES10.glGetIntegerv(GLES10.GL_MAX_ELEMENTS_VERTICES, intBuf16); -// maxVertCount = intBuf16.get(0); -// logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); -// -// GLES10.glGetIntegerv(GLES10.GL_MAX_ELEMENTS_INDICES, intBuf16); -// maxTriCount = intBuf16.get(0); -// logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_TEXTURE_SIZE, intBuf16, 0); - maxTexSize = intBuf16[0]; - logger.log(Level.FINE, "Maximum Texture Resolution: {0}", maxTexSize); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16, 0); - maxCubeTexSize = intBuf16[0]; - logger.log(Level.FINE, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); - - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_RENDERBUFFER_SIZE, intBuf16, 0); - maxRBSize = intBuf16[0]; - logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); - - /* - if (ctxCaps.GL_ARB_color_buffer_float){ - // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. - if (ctxCaps.GL_ARB_half_float_pixel){ - caps.add(Caps.FloatColorBuffer); - } - } - - if (ctxCaps.GL_ARB_depth_buffer_float){ - caps.add(Caps.FloatDepthBuffer); - } - - if (ctxCaps.GL_ARB_draw_instanced) - caps.add(Caps.MeshInstancing); - - if (ctxCaps.GL_ARB_texture_buffer_object) - caps.add(Caps.TextureBuffer); - - if (ctxCaps.GL_ARB_texture_float){ - if (ctxCaps.GL_ARB_half_float_pixel){ - caps.add(Caps.FloatTexture); - } - } - - if (ctxCaps.GL_EXT_packed_float){ - caps.add(Caps.PackedFloatColorBuffer); - if (ctxCaps.GL_ARB_half_float_pixel){ - // because textures are usually uploaded as RGB16F - // need half-float pixel - caps.add(Caps.PackedFloatTexture); - } - } - - if (ctxCaps.GL_EXT_texture_array) - caps.add(Caps.TextureArray); - - if (ctxCaps.GL_EXT_texture_shared_exponent) - caps.add(Caps.SharedExponentTexture); - - if (ctxCaps.GL_EXT_framebuffer_object){ - caps.add(Caps.FrameBuffer); - - glGetInteger(GL_MAX_RENDERBUFFER_SIZE_EXT, intBuf16); - maxRBSize = intBuf16.get(0); - logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); - - glGetInteger(GL_MAX_COLOR_ATTACHMENTS_EXT, intBuf16); - maxFBOAttachs = intBuf16.get(0); - logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs); - - if (ctxCaps.GL_EXT_framebuffer_multisample){ - caps.add(Caps.FrameBufferMultisample); - - glGetInteger(GL_MAX_SAMPLES_EXT, intBuf16); - maxFBOSamples = intBuf16.get(0); - logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples); - } - - if (ctxCaps.GL_ARB_draw_buffers){ - caps.add(Caps.FrameBufferMRT); - glGetInteger(ARBDrawBuffers.GL_MAX_DRAW_BUFFERS_ARB, intBuf16); - maxMRTFBOAttachs = intBuf16.get(0); - logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); - } - } - - if (ctxCaps.GL_ARB_multisample){ - glGetInteger(ARBMultisample.GL_SAMPLE_BUFFERS_ARB, intBuf16); - boolean available = intBuf16.get(0) != 0; - glGetInteger(ARBMultisample.GL_SAMPLES_ARB, intBuf16); - int samples = intBuf16.get(0); - logger.log(Level.FINER, "Samples: {0}", samples); - boolean enabled = glIsEnabled(ARBMultisample.GL_MULTISAMPLE_ARB); - if (samples > 0 && available && !enabled){ - glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); - } - } - */ - - String extensions = JmeIosGLES.glGetString(JmeIosGLES.GL_EXTENSIONS); - logger.log(Level.FINE, "GL_EXTENSIONS: {0}", extensions); - - // Get number of compressed formats available. - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_NUM_COMPRESSED_TEXTURE_FORMATS, intBuf16, 0); - int numCompressedFormats = intBuf16[0]; - - // Allocate buffer for compressed formats. - int[] compressedFormats = new int[numCompressedFormats]; - JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats, 0); - - // Check for errors after all glGet calls. - JmeIosGLES.checkGLError(); - - // Print compressed formats. - for (int i = 0; i < numCompressedFormats; i++) { - logger.log(Level.FINE, "Compressed Texture Formats: {0}", compressedFormats[i]); - } - - TextureUtil.loadTextureFeatures(extensions); - - applyRenderState(RenderState.DEFAULT); - JmeIosGLES.glDisable(JmeIosGLES.GL_DITHER); - JmeIosGLES.checkGLError(); - - logger.log(Level.FINE, "Caps: {0}", caps); - logger.setLevel(store); - - uintIndexSupport = extensions.contains("GL_OES_element_index_uint"); - logger.log(Level.FINE, "Support for UInt index: {0}", uintIndexSupport); - } - - - /* ------------------------------------------------------------------------------ */ - - - private int extractVersion(String prefixStr, String versionStr) { - if (versionStr != null) { - int spaceIdx = versionStr.indexOf(" ", prefixStr.length()); - if (spaceIdx >= 1) { - versionStr = versionStr.substring(prefixStr.length(), spaceIdx).trim(); - } else { - versionStr = versionStr.substring(prefixStr.length()).trim(); - } - float version = Float.parseFloat(versionStr); - return (int) (version * 100); - } else { - return -1; - } - } - - private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - intBuf1[0] = rb.getId(); - JmeIosGLES.glDeleteRenderbuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - } - - private int convertUsage(Usage usage) { - switch (usage) { - case Static: - return JmeIosGLES.GL_STATIC_DRAW; - case Dynamic: - return JmeIosGLES.GL_DYNAMIC_DRAW; - case Stream: - return JmeIosGLES.GL_STREAM_DRAW; - default: - throw new RuntimeException("Unknown usage type."); - } - } - - - protected void bindProgram(Shader shader) { - int shaderId = shader.getId(); - if (context.boundShaderProgram != shaderId) { - JmeIosGLES.glUseProgram(shaderId); - JmeIosGLES.checkGLError(); - - statistics.onShaderUse(shader, true); - boundShader = shader; - context.boundShaderProgram = shaderId; - } else { - statistics.onShaderUse(shader, false); - } - } - - protected void updateShaderUniforms(Shader shader) { - ListMap uniforms = shader.getUniformMap(); - for (int i = 0; i < uniforms.size(); i++) { - Uniform uniform = uniforms.getValue(i); - if (uniform.isUpdateNeeded()) { - updateUniform(shader, uniform); - } - } - } - - - protected void updateUniform(Shader shader, Uniform uniform) { - logger.log(Level.FINE, "IGLESShaderRenderer private updateUniform: " + uniform.getVarType()); - int shaderId = shader.getId(); - - assert uniform.getName() != null; - assert shader.getId() > 0; - - if (context.boundShaderProgram != shaderId) { - JmeIosGLES.glUseProgram(shaderId); - JmeIosGLES.checkGLError(); - - statistics.onShaderUse(shader, true); - boundShader = shader; - context.boundShaderProgram = shaderId; - } else { - statistics.onShaderUse(shader, false); - } - - int loc = uniform.getLocation(); - if (loc == -1) { - return; - } - - if (loc == -2) { - // get uniform location - updateUniformLocation(shader, uniform); - if (uniform.getLocation() == -1) { - // not declared, ignore - uniform.clearUpdateNeeded(); - return; - } - loc = uniform.getLocation(); - } - - if (uniform.getVarType() == null) { - // removed logging the warning to avoid flooding the log - // (LWJGL also doesn't post a warning) - //logger.log(Level.FINEST, "Uniform value is not set yet. Shader: {0}, Uniform: {1}", - // new Object[]{shader.toString(), uniform.toString()}); - return; // value not set yet.. - } - - statistics.onUniformSet(); - - uniform.clearUpdateNeeded(); - ByteBuffer bb;//GetPrimitiveArrayCritical - FloatBuffer fb; - IntBuffer ib; - switch (uniform.getVarType()) { - case Float: - Float f = (Float) uniform.getValue(); - JmeIosGLES.glUniform1f(loc, f.floatValue()); - break; - case Vector2: - Vector2f v2 = (Vector2f) uniform.getValue(); - JmeIosGLES.glUniform2f(loc, v2.getX(), v2.getY()); - break; - case Vector3: - Vector3f v3 = (Vector3f) uniform.getValue(); - JmeIosGLES.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); - break; - case Vector4: - Object val = uniform.getValue(); - if (val instanceof ColorRGBA) { - ColorRGBA c = (ColorRGBA) val; - JmeIosGLES.glUniform4f(loc, c.r, c.g, c.b, c.a); - } else if (val instanceof Vector4f) { - Vector4f c = (Vector4f) val; - JmeIosGLES.glUniform4f(loc, c.x, c.y, c.z, c.w); - } else { - Quaternion c = (Quaternion) uniform.getValue(); - JmeIosGLES.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); - } - break; - case Boolean: - Boolean b = (Boolean) uniform.getValue(); - JmeIosGLES.glUniform1i(loc, b.booleanValue() ? JmeIosGLES.GL_TRUE : JmeIosGLES.GL_FALSE); - break; - case Matrix3: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 9; - JmeIosGLES.glUniformMatrix3fv(loc, 1, false, fb); - break; - case Matrix4: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 16; - JmeIosGLES.glUniformMatrix4fv(loc, 1, false, fb); - break; - case IntArray: - ib = (IntBuffer) uniform.getValue(); - JmeIosGLES.glUniform1iv(loc, ib.limit(), ib); - break; - case FloatArray: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform1fv(loc, fb.limit(), fb); - break; - case Vector2Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform2fv(loc, fb.limit() / 2, fb); - break; - case Vector3Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform3fv(loc, fb.limit() / 3, fb); - break; - case Vector4Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniform4fv(loc, fb.limit() / 4, fb); - break; - case Matrix4Array: - fb = (FloatBuffer) uniform.getValue(); - JmeIosGLES.glUniformMatrix4fv(loc, fb.limit() / 16, false, fb); - break; - case Int: - Integer i = (Integer) uniform.getValue(); - JmeIosGLES.glUniform1i(loc, i.intValue()); - break; - default: - throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); - } - JmeIosGLES.checkGLError(); - } - - protected void updateUniformLocation(Shader shader, Uniform uniform) { - stringBuf.setLength(0); - stringBuf.append(uniform.getName()).append('\0'); - updateNameBuffer(); - int loc = JmeIosGLES.glGetUniformLocation(shader.getId(), uniform.getName()); - JmeIosGLES.checkGLError(); - - if (loc < 0) { - uniform.setLocation(-1); - // uniform is not declared in shader - } else { - uniform.setLocation(loc); - } - } - - protected void updateNameBuffer() { - int len = stringBuf.length(); - - nameBuf.position(0); - nameBuf.limit(len); - for (int i = 0; i < len; i++) { - nameBuf.put((byte) stringBuf.charAt(i)); - } - - nameBuf.rewind(); - } - - - public void updateShaderData(Shader shader) { - int id = shader.getId(); - boolean needRegister = false; - if (id == -1) { - // create program - id = JmeIosGLES.glCreateProgram(); - JmeIosGLES.checkGLError(); - - if (id <= 0) { - throw new RendererException("Invalid ID received when trying to create shader program."); - } - - shader.setId(id); - needRegister = true; - } - - for (ShaderSource source : shader.getSources()) { - if (source.isUpdateNeeded()) { - updateShaderSourceData(source); - } - - JmeIosGLES.glAttachShader(id, source.getId()); - JmeIosGLES.checkGLError(); - } - - // link shaders to program - JmeIosGLES.glLinkProgram(id); - JmeIosGLES.checkGLError(); - - JmeIosGLES.glGetProgramiv(id, JmeIosGLES.GL_LINK_STATUS, intBuf1, 0); - JmeIosGLES.checkGLError(); - - boolean linkOK = intBuf1[0] == JmeIosGLES.GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !linkOK) { - JmeIosGLES.glGetProgramiv(id, JmeIosGLES.GL_INFO_LOG_LENGTH, intBuf1, 0); - JmeIosGLES.checkGLError(); - - int length = intBuf1[0]; - if (length > 3) { - // get infos - infoLog = JmeIosGLES.glGetProgramInfoLog(id); - JmeIosGLES.checkGLError(); - } - } - - if (linkOK) { - if (infoLog != null) { - logger.log(Level.FINE, "shader link success. \n{0}", infoLog); - } else { - logger.fine("shader link success"); - } - shader.clearUpdateNeeded(); - if (needRegister) { - // Register shader for clean up if it was created in this method. - objManager.registerObject(shader); - statistics.onNewShader(); - } else { - // OpenGL spec: uniform locations may change after re-link - resetUniformLocations(shader); - } - } else { - if (infoLog != null) { - throw new RendererException("Shader link failure, shader:" + shader + "\n" + infoLog); - } else { - throw new RendererException("Shader link failure, shader:" + shader + "\ninfo: "); - } - } - } - - protected void resetUniformLocations(Shader shader) { - ListMap uniforms = shader.getUniformMap(); - for (int i = 0; i < uniforms.size(); i++) { - Uniform uniform = uniforms.getValue(i); - uniform.reset(); // e.g check location again - } - } - - public void updateTexImageData(Image img, Texture.Type type) { - int texId = img.getId(); - if (texId == -1) { - // create texture - JmeIosGLES.glGenTextures(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - texId = intBuf1[0]; - img.setId(texId); - objManager.registerObject(img); - - statistics.onNewTexture(); - } - - // bind texture - int target = convertTextureType(type); - if (context.boundTextures[0] != img) { - if (context.boundTextureUnit != 0) { - JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0); - JmeIosGLES.checkGLError(); - - context.boundTextureUnit = 0; - } - - JmeIosGLES.glBindTexture(target, texId); - JmeIosGLES.checkGLError(); - - context.boundTextures[0] = img; - } - - boolean needMips = false; - if (img.isGeneratedMipmapsRequired()) { - needMips = true; - img.setMipmapsGenerated(true); - } - - if (target == JmeIosGLES.GL_TEXTURE_CUBE_MAP) { - // Check max texture size before upload - if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) { - throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize); - } - } else { - if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { - throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); - } - } - - if (target == JmeIosGLES.GL_TEXTURE_CUBE_MAP) { - // Upload a cube map / sky box - /* - @SuppressWarnings("unchecked") - List bmps = (List) img.getEfficentData(); - if (bmps != null) { - // Native android bitmap - if (bmps.size() != 6) { - throw new UnsupportedOperationException("Invalid texture: " + img - + "Cubemap textures must contain 6 data units."); - } - for (int i = 0; i < 6; i++) { - TextureUtil.uploadTextureBitmap(JmeIosGLES.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, bmps.get(i).getBitmap(), needMips); - bmps.get(i).notifyBitmapUploaded(); - } - } else { - */ - // Standard jme3 image data - List data = img.getData(); - if (data.size() != 6) { - throw new UnsupportedOperationException("Invalid texture: " + img - + "Cubemap textures must contain 6 data units."); - } - for (int i = 0; i < 6; i++) { - TextureUtil.uploadTextureAny(img, JmeIosGLES.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, needMips); - } - //} - } else { - TextureUtil.uploadTextureAny(img, target, 0, needMips); - /* - if (img.getEfficentData() instanceof AndroidImageInfo) { - AndroidImageInfo info = (AndroidImageInfo) img.getEfficentData(); - info.notifyBitmapUploaded(); - } - */ - } - - img.clearUpdateNeeded(); - } - - private void setupTextureParams(Texture tex) { - int target = convertTextureType(tex.getType()); - - // filter things - int minFilter = convertMinFilter(tex.getMinFilter()); - int magFilter = convertMagFilter(tex.getMagFilter()); - - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_MIN_FILTER, minFilter); - JmeIosGLES.checkGLError(); - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_MAG_FILTER, magFilter); - JmeIosGLES.checkGLError(); - - /* - if (tex.getAnisotropicFilter() > 1){ - - if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){ - glTexParameterf(target, - EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, - tex.getAnisotropicFilter()); - } - - } - */ - // repeat modes - - switch (tex.getType()) { - case ThreeDimensional: - case CubeMap: // cubemaps use 3D coords - // GL_TEXTURE_WRAP_R is not available in api 8 - //GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); - case TwoDimensional: - case TwoDimensionalArray: - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); - - // fall down here is intentional.. -// case OneDimensional: - JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); - - JmeIosGLES.checkGLError(); - break; - default: - throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); - } - - // R to Texture compare mode -/* - if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off){ - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_MODE, GLES20.GL_COMPARE_R_TO_TEXTURE); - GLES20.glTexParameteri(target, GLES20.GL_DEPTH_TEXTURE_MODE, GLES20.GL_INTENSITY); - if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual){ - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_GEQUAL); - }else{ - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_LEQUAL); - } - } - */ - } - - private int convertTextureType(Texture.Type type) { - switch (type) { - case TwoDimensional: - return JmeIosGLES.GL_TEXTURE_2D; - // case TwoDimensionalArray: - // return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT; -// case ThreeDimensional: - // return GLES20.GL_TEXTURE_3D; - case CubeMap: - return JmeIosGLES.GL_TEXTURE_CUBE_MAP; - default: - throw new UnsupportedOperationException("Unknown texture type: " + type); - } - } - - private int convertMagFilter(Texture.MagFilter filter) { - switch (filter) { - case Bilinear: - return JmeIosGLES.GL_LINEAR; - case Nearest: - return JmeIosGLES.GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown mag filter: " + filter); - } - } - - private int convertMinFilter(Texture.MinFilter filter) { - switch (filter) { - case Trilinear: - return JmeIosGLES.GL_LINEAR_MIPMAP_LINEAR; - case BilinearNearestMipMap: - return JmeIosGLES.GL_LINEAR_MIPMAP_NEAREST; - case NearestLinearMipMap: - return JmeIosGLES.GL_NEAREST_MIPMAP_LINEAR; - case NearestNearestMipMap: - return JmeIosGLES.GL_NEAREST_MIPMAP_NEAREST; - case BilinearNoMipMaps: - return JmeIosGLES.GL_LINEAR; - case NearestNoMipMaps: - return JmeIosGLES.GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown min filter: " + filter); - } - } - - private int convertWrapMode(Texture.WrapMode mode) { - switch (mode) { - case BorderClamp: - case Clamp: - case EdgeClamp: - return JmeIosGLES.GL_CLAMP_TO_EDGE; - case Repeat: - return JmeIosGLES.GL_REPEAT; - case MirroredRepeat: - return JmeIosGLES.GL_MIRRORED_REPEAT; - default: - throw new UnsupportedOperationException("Unknown wrap mode: " + mode); - } - } - - private void renderMeshVertexArray(Mesh mesh, int lod, int count) { - for (VertexBuffer vb : mesh.getBufferList().getArray()) { - if (vb.getBufferType() == Type.InterleavedData - || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers - || vb.getBufferType() == Type.Index) { - continue; - } - - if (vb.getStride() == 0) { - // not interleaved - setVertexAttrib_Array(vb); - } else { - // interleaved - VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); - setVertexAttrib_Array(vb, interleavedData); - } - } - - VertexBuffer indices = null; - if (mesh.getNumLodLevels() > 0) { - indices = mesh.getLodLevel(lod); - } else { - indices = mesh.getBuffer(Type.Index);//buffers.get(Type.Index.ordinal()); - } - if (indices != null) { - drawTriangleList_Array(indices, mesh, count); - } else { - JmeIosGLES.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); - JmeIosGLES.checkGLError(); - } - clearVertexAttribs(); - } - - private void renderMeshDefault(Mesh mesh, int lod, int count) { - VertexBuffer indices = null; - VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); - if (interleavedData != null && interleavedData.isUpdateNeeded()) { - updateBufferData(interleavedData); - } - - //IntMap buffers = mesh.getBuffers(); ; - if (mesh.getNumLodLevels() > 0) { - indices = mesh.getLodLevel(lod); - } else { - indices = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal()); - } - for (VertexBuffer vb : mesh.getBufferList().getArray()){ - if (vb.getBufferType() == Type.InterleavedData - || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers - || vb.getBufferType() == Type.Index) { - continue; - } - - if (vb.getStride() == 0) { - // not interleaved - setVertexAttrib(vb); - } else { - // interleaved - setVertexAttrib(vb, interleavedData); - } - } - if (indices != null) { - drawTriangleList(indices, mesh, count); - } else { -// throw new UnsupportedOperationException("Cannot render without index buffer"); - JmeIosGLES.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); - JmeIosGLES.checkGLError(); - } - clearVertexAttribs(); - } - - - public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { - if (vb.getBufferType() == VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); - } - - if (vb.isUpdateNeeded() && idb == null) { - updateBufferData(vb); - } - - int programId = context.boundShaderProgram; - if (programId > 0) { - Attribute attrib = boundShader.getAttribute(vb.getBufferType()); - int loc = attrib.getLocation(); - if (loc == -1) { - return; // not defined - } - - if (loc == -2) { -// stringBuf.setLength(0); -// stringBuf.append("in").append(vb.getBufferType().name()).append('\0'); -// updateNameBuffer(); - - String attributeName = "in" + vb.getBufferType().name(); - loc = JmeIosGLES.glGetAttribLocation(programId, attributeName); - JmeIosGLES.checkGLError(); - - // not really the name of it in the shader (inPosition\0) but - // the internal name of the enum (Position). - if (loc < 0) { - attrib.setLocation(-1); - return; // not available in shader. - } else { - attrib.setLocation(loc); - } - } - - VertexBuffer[] attribs = context.boundAttribs; - if (!context.attribIndexList.moveToNew(loc)) { - JmeIosGLES.glEnableVertexAttribArray(loc); - JmeIosGLES.checkGLError(); - //System.out.println("Enabled ATTRIB IDX: "+loc); - } - if (attribs[loc] != vb) { - // NOTE: Use id from interleaved buffer if specified - int bufId = idb != null ? idb.getId() : vb.getId(); - assert bufId != -1; - - if (bufId == -1) { - logger.warning("invalid buffer id"); - } - - if (context.boundArrayVBO != bufId) { - JmeIosGLES.glBindBuffer(JmeIosGLES.GL_ARRAY_BUFFER, bufId); - JmeIosGLES.checkGLError(); - - context.boundArrayVBO = bufId; - } - - vb.getData().rewind(); - /* - Android22Workaround.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - 0); - */ - logger.warning("iTODO Android22Workaround"); - - JmeIosGLES.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - null); - - JmeIosGLES.checkGLError(); - - attribs[loc] = vb; - } - } else { - throw new IllegalStateException("Cannot render mesh without shader bound"); - } - } - - public void setVertexAttrib(VertexBuffer vb) { - setVertexAttrib(vb, null); - } - - public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { - /* if (count > 1){ - ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0, - vertCount, count); - }else{*/ - JmeIosGLES.glDrawArrays(convertElementMode(mode), 0, vertCount); - JmeIosGLES.checkGLError(); - /* - }*/ - } - - public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { - if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); - } - - if (indexBuf.isUpdateNeeded()) { - updateBufferData(indexBuf); - } - - int bufId = indexBuf.getId(); - assert bufId != -1; - - if (bufId == -1) { - throw new RendererException("Invalid buffer ID"); - } - - if (context.boundElementArrayVBO != bufId) { - JmeIosGLES.glBindBuffer(JmeIosGLES.GL_ELEMENT_ARRAY_BUFFER, bufId); - JmeIosGLES.checkGLError(); - - context.boundElementArrayVBO = bufId; - } - - int vertCount = mesh.getVertexCount(); - boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - - Buffer indexData = indexBuf.getData(); - - if (!uintIndexSupport && (indexBuf.getFormat() == Format.UnsignedInt)) { - throw new RendererException("OpenGL ES does not support 32-bit index buffers." + - "Split your models to avoid going over 65536 vertices."); - } - - if (mesh.getMode() == Mode.Hybrid) { - int[] modeStart = mesh.getModeStart(); - int[] elementLengths = mesh.getElementLengths(); - - int elMode = convertElementMode(Mode.Triangles); - int fmt = convertVertexBufferFormat(indexBuf.getFormat()); - int elSize = indexBuf.getFormat().getComponentSize(); - int listStart = modeStart[0]; - int stripStart = modeStart[1]; - int fanStart = modeStart[2]; - int curOffset = 0; - for (int i = 0; i < elementLengths.length; i++) { - if (i == stripStart) { - elMode = convertElementMode(Mode.TriangleStrip); - } else if (i == fanStart) { - elMode = convertElementMode(Mode.TriangleStrip); - } - int elementLength = elementLengths[i]; - - if (useInstancing) { - //ARBDrawInstanced. - throw new IllegalArgumentException("instancing is not supported."); - /* - GLES20.glDrawElementsInstancedARB(elMode, - elementLength, - fmt, - curOffset, - count); - */ - } else { - indexBuf.getData().position(curOffset); - JmeIosGLES.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); - JmeIosGLES.checkGLError(); - /* - glDrawRangeElements(elMode, - 0, - vertCount, - elementLength, - fmt, - curOffset); - */ - } - - curOffset += elementLength * elSize; - } - } else { - if (useInstancing) { - throw new IllegalArgumentException("instancing is not supported."); - //ARBDrawInstanced. -/* - GLES20.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - 0, - count); - */ - } else { - logger.log(Level.FINE, "IGLESShaderRenderer drawTriangleList TODO check"); - indexData.rewind(); - JmeIosGLES.glDrawElementsIndex( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - 0); - /*TODO: - indexData.rewind(); - JmeIosGLES.glDrawElements( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - 0); - */ - JmeIosGLES.checkGLError(); - } - } - } - - public int convertElementMode(Mesh.Mode mode) { - switch (mode) { - case Points: - return JmeIosGLES.GL_POINTS; - case Lines: - return JmeIosGLES.GL_LINES; - case LineLoop: - return JmeIosGLES.GL_LINE_LOOP; - case LineStrip: - return JmeIosGLES.GL_LINE_STRIP; - case Triangles: - return JmeIosGLES.GL_TRIANGLES; - case TriangleFan: - return JmeIosGLES.GL_TRIANGLE_FAN; - case TriangleStrip: - return JmeIosGLES.GL_TRIANGLE_STRIP; - default: - throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); - } - } - - - private int convertVertexBufferFormat(Format format) { - switch (format) { - case Byte: - return JmeIosGLES.GL_BYTE; - case UnsignedByte: - return JmeIosGLES.GL_UNSIGNED_BYTE; - case Short: - return JmeIosGLES.GL_SHORT; - case UnsignedShort: - return JmeIosGLES.GL_UNSIGNED_SHORT; - case Int: - return JmeIosGLES.GL_INT; - case UnsignedInt: - return JmeIosGLES.GL_UNSIGNED_INT; - /* - case Half: - return NVHalfFloat.GL_HALF_FLOAT_NV; - // return ARBHalfFloatVertex.GL_HALF_FLOAT; - */ - case Float: - return JmeIosGLES.GL_FLOAT; -// case Double: -// return JmeIosGLES.GL_DOUBLE; - default: - throw new RuntimeException("Unknown buffer format."); - - } - } - - public void clearVertexAttribs() { - IDList attribList = context.attribIndexList; - for (int i = 0; i < attribList.oldLen; i++) { - int idx = attribList.oldList[i]; - - JmeIosGLES.glDisableVertexAttribArray(idx); - JmeIosGLES.checkGLError(); - - context.boundAttribs[idx] = null; - } - context.attribIndexList.copyNewToOld(); - } - - public void updateFrameBuffer(FrameBuffer fb) { - int id = fb.getId(); - if (id == -1) { - // create FBO - JmeIosGLES.glGenFramebuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - id = intBuf1[0]; - fb.setId(id); - objManager.registerObject(fb); - - statistics.onNewFrameBuffer(); - } - - if (context.boundFBO != id) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, id); - JmeIosGLES.checkGLError(); - - // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0 - context.boundDrawBuf = 0; - context.boundFBO = id; - } - - FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); - if (depthBuf != null) { - updateFrameBufferAttachment(fb, depthBuf); - } - - for (int i = 0; i < fb.getNumColorBuffers(); i++) { - FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); - updateFrameBufferAttachment(fb, colorBuf); - } - - fb.clearUpdateNeeded(); - } - - - public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) { - boolean needAttach; - if (rb.getTexture() == null) { - // if it hasn't been created yet, then attach is required. - needAttach = rb.getId() == -1; - updateRenderBuffer(fb, rb); - } else { - needAttach = false; - updateRenderTexture(fb, rb); - } - if (needAttach) { - JmeIosGLES.glFramebufferRenderbuffer(JmeIosGLES.GL_FRAMEBUFFER, - convertAttachmentSlot(rb.getSlot()), - JmeIosGLES.GL_RENDERBUFFER, - rb.getId()); - - JmeIosGLES.checkGLError(); - } - } - - - public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { - Texture tex = rb.getTexture(); - Image image = tex.getImage(); - if (image.isUpdateNeeded()) { - updateTexImageData(image, tex.getType()); - - // NOTE: For depth textures, sets nearest/no-mips mode - // Required to fix "framebuffer unsupported" - // for old NVIDIA drivers! - setupTextureParams(tex); - } - - JmeIosGLES.glFramebufferTexture2D(JmeIosGLES.GL_FRAMEBUFFER, - convertAttachmentSlot(rb.getSlot()), - convertTextureType(tex.getType()), - image.getId(), - 0); - - JmeIosGLES.checkGLError(); - } - - - private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - int id = rb.getId(); - if (id == -1) { - JmeIosGLES.glGenRenderbuffers(1, intBuf1, 0); - JmeIosGLES.checkGLError(); - - id = intBuf1[0]; - rb.setId(id); - } - - if (context.boundRB != id) { - JmeIosGLES.glBindRenderbuffer(JmeIosGLES.GL_RENDERBUFFER, id); - JmeIosGLES.checkGLError(); - - context.boundRB = id; - } - - if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { - throw new RendererException("Resolution " + fb.getWidth() - + ":" + fb.getHeight() + " is not supported."); - } - - TextureUtil.IosGLImageFormat imageFormat = TextureUtil.getImageFormat(rb.getFormat()); - if (imageFormat.renderBufferStorageFormat == 0) { - throw new RendererException("The format '" + rb.getFormat() + "' cannot be used for renderbuffers."); - } - -// if (fb.getSamples() > 1 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample) { - if (fb.getSamples() > 1) { -// // FIXME - throw new RendererException("Multisample FrameBuffer is not supported yet."); -// int samples = fb.getSamples(); -// if (maxFBOSamples < samples) { -// samples = maxFBOSamples; -// } -// glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, -// samples, -// glFmt.internalFormat, -// fb.getWidth(), -// fb.getHeight()); - } else { - JmeIosGLES.glRenderbufferStorage(JmeIosGLES.GL_RENDERBUFFER, - imageFormat.renderBufferStorageFormat, - fb.getWidth(), - fb.getHeight()); - - JmeIosGLES.checkGLError(); - } - } - - - private int convertAttachmentSlot(int attachmentSlot) { - // can also add support for stencil here - if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { - return JmeIosGLES.GL_DEPTH_ATTACHMENT; - } else if (attachmentSlot == 0) { - return JmeIosGLES.GL_COLOR_ATTACHMENT0; - } else { - throw new UnsupportedOperationException("Android does not support multiple color attachments to an FBO"); - } - } - - - private void checkFrameBufferStatus(FrameBuffer fb) { - try { - checkFrameBufferError(); - } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb); - printRealFrameBufferInfo(fb); - throw ex; - } - } - - private void checkFrameBufferError() { - int status = JmeIosGLES.glCheckFramebufferStatus(JmeIosGLES.GL_FRAMEBUFFER); - switch (status) { - case JmeIosGLES.GL_FRAMEBUFFER_COMPLETE: - break; - case JmeIosGLES.GL_FRAMEBUFFER_UNSUPPORTED: - //Choose different formats - throw new IllegalStateException("Framebuffer object format is " - + "unsupported by the video hardware."); - case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - throw new IllegalStateException("Framebuffer has erronous attachment."); - case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); - case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: - throw new IllegalStateException("Framebuffer attachments must have same dimensions."); -// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_FORMATS: -// throw new IllegalStateException("Framebuffer attachments must have same formats."); -// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: -// throw new IllegalStateException("Incomplete draw buffer."); -// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: -// throw new IllegalStateException("Incomplete read buffer."); -// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: -// throw new IllegalStateException("Incomplete multisample buffer."); - default: - //Programming error; will fail on all hardware - throw new IllegalStateException("Some video driver error " - + "or programming error occured. " - + "Framebuffer object status is invalid: " + status); - } - } - - private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name) { - System.out.println("== Renderbuffer " + name + " =="); - System.out.println("RB ID: " + rb.getId()); - System.out.println("Is proper? " + JmeIosGLES.glIsRenderbuffer(rb.getId())); - - int attachment = convertAttachmentSlot(rb.getSlot()); - - //intBuf16.clear(); - JmeIosGLES.glGetFramebufferAttachmentParameteriv(JmeIosGLES.GL_FRAMEBUFFER, - attachment, JmeIosGLES.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, intBuf16, 0); - int type = intBuf16[0]; - - //intBuf16.clear(); - JmeIosGLES.glGetFramebufferAttachmentParameteriv(JmeIosGLES.GL_FRAMEBUFFER, - attachment, JmeIosGLES.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, intBuf16, 0); - int rbName = intBuf16[0]; - - switch (type) { - case JmeIosGLES.GL_NONE: - System.out.println("Type: None"); - break; - case JmeIosGLES.GL_TEXTURE: - System.out.println("Type: Texture"); - break; - case JmeIosGLES.GL_RENDERBUFFER: - System.out.println("Type: Buffer"); - System.out.println("RB ID: " + rbName); - break; - } - } - - private void printRealFrameBufferInfo(FrameBuffer fb) { -// boolean doubleBuffer = GLES20.glGetBooleanv(GLES20.GL_DOUBLEBUFFER); - boolean doubleBuffer = false; // FIXME -// String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER)); -// String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER)); - - int fbId = fb.getId(); - //intBuf16.clear(); -// int curDrawBinding = GLES20.glGetIntegerv(GLES20.GL_DRAW_FRAMEBUFFER_BINDING); -// int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING); - - System.out.println("=== OpenGL FBO State ==="); - System.out.println("Context doublebuffered? " + doubleBuffer); - System.out.println("FBO ID: " + fbId); - System.out.println("Is proper? " + JmeIosGLES.glIsFramebuffer(fbId)); -// System.out.println("Is bound to draw? " + (fbId == curDrawBinding)); -// System.out.println("Is bound to read? " + (fbId == curReadBinding)); -// System.out.println("Draw buffer: " + drawBuf); -// System.out.println("Read buffer: " + readBuf); - - if (context.boundFBO != fbId) { - JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, fbId); - context.boundFBO = fbId; - } - - if (fb.getDepthBuffer() != null) { - printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth"); - } - for (int i = 0; i < fb.getNumColorBuffers(); i++) { - printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i); - } - - - } - - public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int count) { - if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); - } - - boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - if (useInstancing) { - throw new IllegalArgumentException("Caps.MeshInstancing is not supported."); - } - - int vertCount = mesh.getVertexCount(); - Buffer indexData = indexBuf.getData(); - indexData.rewind(); - - if (mesh.getMode() == Mode.Hybrid) { - int[] modeStart = mesh.getModeStart(); - int[] elementLengths = mesh.getElementLengths(); - - int elMode = convertElementMode(Mode.Triangles); - int fmt = convertVertexBufferFormat(indexBuf.getFormat()); - int elSize = indexBuf.getFormat().getComponentSize(); - int listStart = modeStart[0]; - int stripStart = modeStart[1]; - int fanStart = modeStart[2]; - int curOffset = 0; - for (int i = 0; i < elementLengths.length; i++) { - if (i == stripStart) { - elMode = convertElementMode(Mode.TriangleStrip); - } else if (i == fanStart) { - elMode = convertElementMode(Mode.TriangleFan); - } - int elementLength = elementLengths[i]; - - indexBuf.getData().position(curOffset); - JmeIosGLES.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); - JmeIosGLES.checkGLError(); - - curOffset += elementLength * elSize; - } - } else { - JmeIosGLES.glDrawElements( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - indexBuf.getData()); - JmeIosGLES.checkGLError(); - } - } - - public void setVertexAttrib_Array(VertexBuffer vb, VertexBuffer idb) { - if (vb.getBufferType() == VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); - } - - // Get shader - int programId = context.boundShaderProgram; - if (programId > 0) { - VertexBuffer[] attribs = context.boundAttribs; - - Attribute attrib = boundShader.getAttribute(vb.getBufferType()); - int loc = attrib.getLocation(); - if (loc == -1) { - //throw new IllegalArgumentException("Location is invalid for attrib: [" + vb.getBufferType().name() + "]"); - return; - } else if (loc == -2) { - String attributeName = "in" + vb.getBufferType().name(); - - loc = JmeIosGLES.glGetAttribLocation(programId, attributeName); - JmeIosGLES.checkGLError(); - - if (loc < 0) { - attrib.setLocation(-1); - return; // not available in shader. - } else { - attrib.setLocation(loc); - } - - } // if (loc == -2) - - if ((attribs[loc] != vb) || vb.isUpdateNeeded()) { - // NOTE: Use data from interleaved buffer if specified - VertexBuffer avb = idb != null ? idb : vb; - avb.getData().rewind(); - avb.getData().position(vb.getOffset()); - - // Upload attribute data - JmeIosGLES.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - avb.getData()); - - JmeIosGLES.checkGLError(); - - JmeIosGLES.glEnableVertexAttribArray(loc); - JmeIosGLES.checkGLError(); - - attribs[loc] = vb; - } // if (attribs[loc] != vb) - } else { - throw new IllegalStateException("Cannot render mesh without shader bound"); - } - } - - public void setVertexAttrib_Array(VertexBuffer vb) { - setVertexAttrib_Array(vb, null); - } - - - public void updateShaderSourceData(ShaderSource source) { - int id = source.getId(); - if (id == -1) { - // Create id - id = JmeIosGLES.glCreateShader(convertShaderType(source.getType())); - JmeIosGLES.checkGLError(); - - if (id <= 0) { - throw new RendererException("Invalid ID received when trying to create shader."); - } - source.setId(id); - } - - if (!source.getLanguage().equals("GLSL100")) { - throw new RendererException("This shader cannot run in OpenGL ES. " - + "Only GLSL 1.0 shaders are supported."); - } - - // upload shader source - // merge the defines and source code - byte[] definesCodeData = source.getDefines().getBytes(); - byte[] sourceCodeData = source.getSource().getBytes(); - ByteBuffer codeBuf = BufferUtils.createByteBuffer(definesCodeData.length - + sourceCodeData.length); - codeBuf.put(definesCodeData); - codeBuf.put(sourceCodeData); - codeBuf.flip(); - - if (powerVr && source.getType() == ShaderType.Vertex) { - // XXX: This is to fix a bug in old PowerVR, remove - // when no longer applicable. - JmeIosGLES.glShaderSource( - id, source.getDefines() - + source.getSource()); - } else { - String precision =""; - if (source.getType() == ShaderType.Fragment) { - precision = "precision mediump float;\n"; - } - JmeIosGLES.glShaderSource( - id, - precision - +source.getDefines() - + source.getSource()); - } -// int range[] = new int[2]; -// int precision[] = new int[1]; -// GLES20.glGetShaderPrecisionFormat(GLES20.GL_VERTEX_SHADER, GLES20.GL_HIGH_FLOAT, range, 0, precision, 0); -// System.out.println("PRECISION HIGH FLOAT VERTEX"); -// System.out.println("range "+range[0]+"," +range[1]); -// System.out.println("precision "+precision[0]); - - JmeIosGLES.glCompileShader(id); - JmeIosGLES.checkGLError(); - - JmeIosGLES.glGetShaderiv(id, JmeIosGLES.GL_COMPILE_STATUS, intBuf1, 0); - JmeIosGLES.checkGLError(); - - boolean compiledOK = intBuf1[0] == JmeIosGLES.GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !compiledOK) { - // even if compile succeeded, check - // log for warnings - JmeIosGLES.glGetShaderiv(id, JmeIosGLES.GL_INFO_LOG_LENGTH, intBuf1, 0); - JmeIosGLES.checkGLError(); - infoLog = JmeIosGLES.glGetShaderInfoLog(id); - } - - if (compiledOK) { - if (infoLog != null) { - logger.log(Level.FINE, "compile success: {0}, {1}", new Object[]{source.getName(), infoLog}); - } else { - logger.log(Level.FINE, "compile success: {0}", source.getName()); - } - source.clearUpdateNeeded(); - } else { - logger.log(Level.WARNING, "Bad compile of:\n{0}", - new Object[]{ShaderDebug.formatShaderSource(stringBuf.toString() + source.getDefines() + source.getSource())}); - if (infoLog != null) { - throw new RendererException("compile error in: " + source + "\n" + infoLog); - } else { - throw new RendererException("compile error in: " + source + "\nerror: "); - } - } - } - - - public int convertShaderType(ShaderType type) { - switch (type) { - case Fragment: - return JmeIosGLES.GL_FRAGMENT_SHADER; - case Vertex: - return JmeIosGLES.GL_VERTEX_SHADER; -// case Geometry: -// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; - default: - throw new RuntimeException("Unrecognized shader type."); - } - } - - private int convertTestFunction(RenderState.TestFunction testFunc) { - switch (testFunc) { - case Never: - return JmeIosGLES.GL_NEVER; - case Less: - return JmeIosGLES.GL_LESS; - case LessOrEqual: - return JmeIosGLES.GL_LEQUAL; - case Greater: - return JmeIosGLES.GL_GREATER; - case GreaterOrEqual: - return JmeIosGLES.GL_GEQUAL; - case Equal: - return JmeIosGLES.GL_EQUAL; - case NotEqual: - return JmeIosGLES.GL_NOTEQUAL; - case Always: - return JmeIosGLES.GL_ALWAYS; - default: - throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); - } - } - - public void setMainFrameBufferSrgb(boolean srgb) { - - } - - public void setLinearizeSrgbImages(boolean linearize) { - - } - - public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { - throw new UnsupportedOperationException("Not supported yet. URA will make that work seamlessly"); - } -} \ No newline at end of file diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java index d3276972d..a9398f159 100644 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java @@ -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) { diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java deleted file mode 100644 index d11308c2a..000000000 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java +++ /dev/null @@ -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); - } - } - - /** - * uploadTextureBitmap uploads a native android bitmap - */ - /* - public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) { - uploadTextureBitmap(target, bitmap, needMips, false, 0, 0); - } - - /** - * uploadTextureBitmap 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]; - } - } -} diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java index 7feaeae5b..2d046550e 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java @@ -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(); diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java index 4ec649086..e47de3a50 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java @@ -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); } diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java index 904db71cf..d29f76f62 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java @@ -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) { diff --git a/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg new file mode 100644 index 000000000..e9d79a459 --- /dev/null +++ b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg @@ -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 diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 82b8ee72b..bf99c84eb 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -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); + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java index 89139282a..2c6a63fd7 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java @@ -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); diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java new file mode 100644 index 000000000..159000a6c --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java @@ -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); + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java new file mode 100644 index 000000000..acc540273 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java @@ -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); + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 1286323ef..c88f7b734 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -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()); diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java new file mode 100644 index 000000000..c8f329f13 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java @@ -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 constMap = new HashMap(); + 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)); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Client.java b/jme3-networking/src/main/java/com/jme3/network/Client.java index 6837a4d86..dd9873238 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Client.java +++ b/jme3-networking/src/main/java/com/jme3/network/Client.java @@ -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. diff --git a/jme3-networking/src/main/java/com/jme3/network/Server.java b/jme3-networking/src/main/java/com/jme3/network/Server.java index 72926eab7..a52cb33bf 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Server.java +++ b/jme3-networking/src/main/java/com/jme3/network/Server.java @@ -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. */ diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index 54e8fd7f9..c0cc2e616 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -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 ); } diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index 3816fbace..0a9ac0ef1 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -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(); private List connectionListeners = new CopyOnWriteArrayList(); + 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 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] diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java new file mode 100644 index 000000000..da7f2a7cf --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java @@ -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. + * + *

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.

+ * + * @author Paul Speed + */ +@Serializable +public class SerializerRegistrationsMessage extends AbstractMessage { + + static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); + + public static final Set ignore = new HashSet(); + 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 list = new ArrayList(); + 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 + "]"; + } + } +} + + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java new file mode 100644 index 000000000..bd1836451 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java @@ -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 + implements ClientService { + + protected AbstractClientService() { + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java new file mode 100644 index 000000000..789767b73 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java @@ -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 + 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) { + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java new file mode 100644 index 000000000..84df3d81b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -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 implements Service { + + 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 getService( Class 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 + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java new file mode 100644 index 000000000..c8057cb31 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java @@ -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 { + + /** + * 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 ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java new file mode 100644 index 000000000..dc204fb2f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java @@ -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 { + + 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); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java new file mode 100644 index 000000000..f522b72d6 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java @@ -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, 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 ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java new file mode 100644 index 000000000..3606ba44b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java @@ -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 { + + 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); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/Service.java b/jme3-networking/src/main/java/com/jme3/network/service/Service.java new file mode 100644 index 000000000..f56c90dda --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/Service.java @@ -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 { + + /** + * 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 ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java new file mode 100644 index 000000000..b8ee7d3c4 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -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 { + + private List> services = new CopyOnWriteArrayList>(); + 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> 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 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 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 > 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 > 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 s : services ) { + s.terminate(getParent()); + } + } + + /** + * Retrieves the first service of the specified type. + */ + public > S getService( Class type ) { + for( Service s : services ) { + if( type.isInstance(s) ) { + return type.cast(s); + } + } + return null; + } + + @Override + public String toString() { + return getClass().getName() + "[services=" + services + "]"; + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java new file mode 100644 index 000000000..d9ea134e1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java @@ -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); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java new file mode 100644 index 000000000..b78316bf3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java @@ -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 handlers = new ConcurrentHashMap(); + + /** + * 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 responses = new ConcurrentHashMap(); + + /** + * 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; + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java new file mode 100644 index 000000000..d7d99981d --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java @@ -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 ); +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java new file mode 100644 index 000000000..4a347b5a3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java @@ -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(). + * + *

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.

+ */ + 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); + } + +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java new file mode 100644 index 000000000..70f12f1e0 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java @@ -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 + + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java new file mode 100644 index 000000000..efb0def6a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java @@ -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 + + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java new file mode 100644 index 000000000..911ce0fb1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -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 { + + @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(); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java new file mode 100644 index 000000000..b24a8db5f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java @@ -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); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java new file mode 100644 index 000000000..a73496ea8 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java @@ -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 + implements MessageListener { + + static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName()); + + private Class delegateType; + private Map methods = new HashMap(); + 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)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 map( String... methodNames ) { + Set names = new HashSet( 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 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 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()); + } + } +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java new file mode 100644 index 000000000..b92ee09a8 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java @@ -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 extends AbstractMessageDelegator { + + 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. + *

Methods of the following signatures are allowed: + *

    + *
  • void someName(S conn, SomeMessage msg) + *
  • void someName(Message msg) + *
+ * 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; + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java new file mode 100644 index 000000000..f2bee3373 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java @@ -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 { + + 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. + *

Methods of the following signatures are allowed: + *

    + *
  • void someName(S conn, SomeMessage msg) + *
  • void someName(Message msg) + *
+ * 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; + } +} + diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java index 06a4ec795..35375f9c5 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java @@ -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 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"; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java new file mode 100644 index 000000000..fd157cb48 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java @@ -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 objectMap = new HashMap(); + + private final List animStacks = new ArrayList(); + private final List bindPoses = new ArrayList(); + + @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(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 bindPoseData = bindPose.getJmeObject(); + logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size()); + for (Map.Entry 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 pairs = new HashMap(); + for (FbxAnimStack stack : animStacks) { + for (FbxAnimLayer layer : stack.getLayers()) { + for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) { + for (Map.Entry 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java index e767763e1..491301c22 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java @@ -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; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java new file mode 100644 index 000000000..d266dcf95 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java @@ -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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java new file mode 100644 index 000000000..e8dbe7fc0 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java @@ -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 influencedNodePropertiesMap = new HashMap(); + private final Map propertyToCurveMap = new HashMap(); + private final Map propertyToDefaultMap = new HashMap(); + + 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 getInfluencedNodeProperties() { + return influencedNodePropertiesMap; + } + + public Collection 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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java new file mode 100644 index 000000000..872626615 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java @@ -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 animCurves = new ArrayList(); + + 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 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); + } +} + \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java new file mode 100644 index 000000000..ea7dbb814 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java @@ -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 getInfluencedNodes() { +// HashSet influencedNodes = new HashSet(); +// 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); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java new file mode 100644 index 000000000..c376757de --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java @@ -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"; +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java new file mode 100644 index 000000000..78d37c74e --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java @@ -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> { + + private final Map bindPose = new HashMap(); + + 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 toJmeObject() { + return bindPose; + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java new file mode 100644 index 000000000..3065ce88c --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java @@ -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); + } +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java new file mode 100644 index 000000000..4b41b4f47 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java @@ -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 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 bones = new ArrayList(); + + 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java new file mode 100644 index 000000000..7a831cf7f --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java @@ -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> { + + private final List clusters = new ArrayList(); + + public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected List 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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java new file mode 100644 index 000000000..7b7b5ee5b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java @@ -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 animCurves = new HashMap(); + + public long[] getKeyTimes() { + Set keyFrameTimesSet = new HashSet(); + 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java similarity index 73% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java index f309a5052..00619dce7 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java @@ -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 createUidToObjectMap(FBXFile file) { - Map uidToObjectMap = new HashMap(); - for (FBXElement rootElement : file.rootElements) { + private static Map createUidToObjectMap(FbxFile file) { + Map uidToObjectMap = new HashMap(); + 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 uidToObjectMap = createUidToObjectMap(file); + public static void dumpFile(FbxFile file, OutputStream out) { + Map 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 uidToObjectMap) { + Map 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 uidToObjectMap) { + protected static void dumpElement(FbxElement el, PrintStream ps, + int indent, Map 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 + "}"); } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java new file mode 100644 index 000000000..1a4d09d53 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java @@ -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 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 children = new ArrayList(); + + public FbxElement(int propsCount) { + this.properties = new ArrayList(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 getFbxProperties() { + List props = new ArrayList(); + 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 values = new ArrayList(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() + "]"; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXFile.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java similarity index 87% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXFile.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java index ba81f1a74..9435b4c4e 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXFile.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java @@ -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 rootElements = new ArrayList(); + public List rootElements = new ArrayList(); public long version; + @Override + public String toString() { + return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]"; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java new file mode 100644 index 000000000..e3c753052 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java @@ -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; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXReader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java similarity index 94% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXReader.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java index b39dfef1c..22e93d8db 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXReader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java @@ -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) { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java new file mode 100644 index 000000000..518dd4134 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java @@ -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); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java new file mode 100644 index 000000000..9be5bf70c --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java @@ -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 { + + 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java new file mode 100644 index 000000000..14d23900d --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java @@ -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 propertyMetaMap = new HashMap(); + + private final Map propertyValueMap = new HashMap(); + + 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; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java new file mode 100644 index 000000000..4569dad33 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java @@ -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 { + + 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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java new file mode 100644 index 000000000..d1b9d3860 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java @@ -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 references = + new EnumMap(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 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java new file mode 100644 index 000000000..bffd94c65 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java @@ -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 (isn't actually defined in FBX) + BoneWeight, // List 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 indexTypes = new HashSet(); + + 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; + } +} + diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java new file mode 100644 index 000000000..5dd911bed --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java @@ -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> { + + 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[] boneIndices; + private ArrayList[] boneWeights; + + private FbxSkinDeformer skinDeformer; + + public FbxMesh(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + + List layerElementsList = new ArrayList(); + List layersList = new ArrayList(); + + 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 boneIndicesForVertex = boneIndices[positionIndex]; + ArrayList boneWeightsForVertex = boneWeights[positionIndex]; + + if (boneIndicesForVertex == null) { + boneIndicesForVertex = new ArrayList(); + boneWeightsForVertex = new ArrayList(); + 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 polygonList = new ArrayList(); + + boolean finishPolygon = false; + List vertexIndices = new ArrayList(); + + 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 boneIndices, List 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 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 irMeshes = IrUtils.splitByMaterial(irMesh); + + // Create a jME3 Mesh for each material index. + IntMap jmeMeshes = new IntMap(); + for (IntMap.Entry 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 boneIndicesForVertex = boneIndices[positionIndex]; + ArrayList 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java new file mode 100644 index 000000000..61fb001dd --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java @@ -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; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java similarity index 67% rename from jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java rename to jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java index eeeee872c..bb7773785 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java @@ -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 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 children = new ArrayList(); - - public FBXElement(int propsCount) { - properties = new ArrayList(propsCount); - propertiesTypes = new char[propsCount]; - } +public final class FbxPolygon { + + int[] indices; + + @Override + public String toString() { + return Arrays.toString(indices); + } + + private static int[] listToArray(List 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 indices) { + FbxPolygon poly = new FbxPolygon(); + poly.indices = listToArray(indices); + return poly; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java new file mode 100644 index 000000000..3a815df6a --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java @@ -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 timeModeToFps = new HashMap(); + + 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: + *
    + *
  • Units are specified in meters.
  • + *
  • Orientation is right-handed with Y-up.
  • + *
+ */ + 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; + } + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java new file mode 100644 index 000000000..c0ce06d4b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java @@ -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 { + + 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: + * jmeChildScale = jmeParentScale / fbxChildScale + */ + NoParentScale + } + + private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation; + + protected FbxNode parent; + protected List children = new ArrayList(); + protected List materials = new ArrayList(); + protected Map userData = new HashMap(); + protected Map> propertyToAnimCurveMap = new HashMap>(); + 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 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 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 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 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 curveNodes = propertyToAnimCurveMap.get(property); + if (curveNodes == null) { + curveNodes = new ArrayList(); + 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); + } + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java new file mode 100644 index 000000000..b63e4bc08 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java @@ -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 extends FbxObject { + public FbxNodeAttribute(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java new file mode 100644 index 000000000..8c35e45e9 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java @@ -0,0 +1,61 @@ +/* + * 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.math.FastMath; +import com.jme3.math.Quaternion; + +public class FbxNodeUtil { + public static Quaternion quatFromBoneAngles(float xAngle, float yAngle, float zAngle) { + float angle; + float sinY, sinZ, sinX, cosY, cosZ, cosX; + angle = zAngle * 0.5f; + sinZ = FastMath.sin(angle); + cosZ = FastMath.cos(angle); + angle = yAngle * 0.5f; + sinY = FastMath.sin(angle); + cosY = FastMath.cos(angle); + angle = xAngle * 0.5f; + sinX = FastMath.sin(angle); + cosX = FastMath.cos(angle); + float cosYXcosZ = cosY * cosZ; + float sinYXsinZ = sinY * sinZ; + float cosYXsinZ = cosY * sinZ; + float sinYXcosZ = sinY * cosZ; + // For some reason bone space is differ, this is modified formulas + float w = (cosYXcosZ * cosX + sinYXsinZ * sinX); + float x = (cosYXcosZ * sinX - sinYXsinZ * cosX); + float y = (sinYXcosZ * cosX + cosYXsinZ * sinX); + float z = (cosYXsinZ * cosX - sinYXcosZ * sinX); + return new Quaternion(x, y, z, w).normalizeLocal(); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java new file mode 100644 index 000000000..ce95546d8 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java @@ -0,0 +1,59 @@ +/* + * 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 class FbxNullAttribute extends FbxNodeAttribute { + + public FbxNullAttribute(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected Object toJmeObject() { + // No data in a "Null" attribute. + return new Object(); + } + + @Override + public void connectObject(FbxObject object) { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + throw new UnsupportedOperationException(); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java new file mode 100644 index 000000000..83db50283 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java @@ -0,0 +1,45 @@ +/* + * 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.file.FbxId; + +public class FbxRootNode extends FbxNode { + public FbxRootNode(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + this.id = FbxId.ROOT; + this.className = "Model"; + this.name = "Scene"; + this.subclassName = ""; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java new file mode 100644 index 000000000..d9439a2d4 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java @@ -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.obj; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.file.FbxId; +import java.util.logging.Logger; + +public abstract class FbxObject { + + private static final Logger logger = Logger.getLogger(FbxObject.class.getName()); + + protected AssetManager assetManager; + protected String sceneFolderName; + + protected FbxId id; + protected String name; + protected String className; + protected String subclassName; + + protected JT jmeObject; // lazily initialized + + protected FbxObject(AssetManager assetManager, String sceneFolderName) { + this.assetManager = assetManager; + this.sceneFolderName = sceneFolderName; + } + + public FbxId getId() { + return id; + } + + public String getName() { + return name; + } + + public String getClassName() { + return className; + } + + public String getSubclassName() { + return subclassName; + } + + public String getFullClassName() { + if (subclassName.equals("")) { + return className; + } else { + return subclassName + " : " + className; + } + } + + @Override + public String toString() { + return name + " (" + id + ")"; + } + + protected void fromElement(FbxElement element) { + id = FbxId.getObjectId(element); + String nameAndClass; + if (element.propertiesTypes.length == 3) { + nameAndClass = (String) element.properties.get(1); + subclassName = (String) element.properties.get(2); + } else if (element.propertiesTypes.length == 2) { + nameAndClass = (String) element.properties.get(0); + subclassName = (String) element.properties.get(1); + } else { + throw new UnsupportedOperationException("This is not an FBX object: " + element.id); + } + + int splitter = nameAndClass.indexOf("\u0000\u0001"); + + if (splitter != -1) { + name = nameAndClass.substring(0, splitter); + className = nameAndClass.substring(splitter + 2); + } else { + name = nameAndClass; + className = null; + } + } + + public final JT getJmeObject() { + if (jmeObject == null) { + jmeObject = toJmeObject(); + if (jmeObject == null) { + throw new UnsupportedOperationException("FBX object subclass " + + "failed to resolve to a jME3 object"); + } + } + return jmeObject; + } + + public final boolean isJmeObjectCreated() { + return jmeObject != null; + } + + protected final void unsupportedConnectObject(FbxObject object) { + throw new IllegalArgumentException("Cannot attach objects of this class (" + + object.getFullClassName() + + ") to " + getClass().getSimpleName()); + } + + protected final void unsupportedConnectObjectProperty(FbxObject object, String property) { + throw new IllegalArgumentException("Cannot attach objects of this class (" + + object.getFullClassName() + + ") to property " + getClass().getSimpleName() + + "[\"" + property + "\"]"); + } + + protected abstract JT toJmeObject(); + + public abstract void connectObject(FbxObject object); + + public abstract void connectObjectProperty(FbxObject object, String property); +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java new file mode 100644 index 000000000..ec8c1fd67 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java @@ -0,0 +1,209 @@ +/* + * 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.obj; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurve; +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.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.node.FbxNode; +import com.jme3.scene.plugins.fbx.node.FbxNullAttribute; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Responsible for producing FBX objects given an FBXElement. + */ +public final class FbxObjectFactory { + + private static final Logger logger = Logger.getLogger(FbxObjectFactory.class.getName()); + + private static Class getImplementingClass(String elementName, String subclassName) { + if (elementName.equals("NodeAttribute")) { + if (subclassName.equals("Root")) { + // Root of skeleton, may not actually be set. + return FbxNullAttribute.class; + } else if (subclassName.equals("LimbNode")) { + // Specifies some limb attributes, optional. + return FbxNullAttribute.class; + } else if (subclassName.equals("Null")) { + // An "Empty" or "Node" without any specific behavior. + return FbxNullAttribute.class; + } else if (subclassName.equals("IKEffector") || + subclassName.equals("FKEffector")) { + // jME3 does not support IK. + return FbxNullAttribute.class; + } else { + // NodeAttribute - Unknown + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Geometry") && subclassName.equals("Mesh")) { + // NodeAttribute - Mesh Data + return FbxMesh.class; + } else if (elementName.equals("Model")) { + // Scene Graph Node + // Determine specific subclass (e.g. Mesh, Null, or LimbNode?) + if (subclassName.equals("LimbNode")) { + return FbxLimbNode.class; // Child Bone of Skeleton? + } else { + return FbxNode.class; + } + } else if (elementName.equals("Pose")) { + if (subclassName.equals("BindPose")) { + // Bind Pose Information + return FbxBindPose.class; + } else { + // Rest Pose Information + // OR + // Other Data (???) + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Material")) { + return FbxMaterial.class; + } else if (elementName.equals("Deformer")) { + // Deformer + if (subclassName.equals("Skin")) { + // FBXSkinDeformer (mapping between FBXMesh & FBXClusters) + return FbxSkinDeformer.class; + } else if (subclassName.equals("Cluster")) { + // Cluster (aka mapping between FBXMesh vertices & weights for bone) + return FbxCluster.class; + } else { + logger.log(Level.WARNING, "Unknown deformer subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Video")) { + if (subclassName.equals("Clip")) { + return FbxImage.class; + } else { + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + return FbxUnknownObject.class; + } + } else if (elementName.equals("Texture")) { + return FbxTexture.class; + } else if (elementName.equals("AnimationStack")) { + // AnimationStack (jME Animation) + return FbxAnimStack.class; + } else if (elementName.equals("AnimationLayer")) { + // AnimationLayer (for blended animation - not supported) + return FbxAnimLayer.class; + } else if (elementName.equals("AnimationCurveNode")) { + // AnimationCurveNode + return FbxAnimCurveNode.class; + } else if (elementName.equals("AnimationCurve")) { + // AnimationCurve (Data) + return FbxAnimCurve.class; + } else if (elementName.equals("SceneInfo")) { + // Old-style FBX 6.1 uses this. Nothing useful here. + return FbxUnknownObject.class; + } else { + logger.log(Level.WARNING, "Unknown object class: {0}. Ignoring.", elementName); + return FbxUnknownObject.class; + } + } + + /** + * Automatically create an FBXObject by inspecting its class / subclass + * properties. + * + * @param element The element from which to create an object. + * @param assetManager AssetManager to load dependent resources + * @param sceneFolderName Folder relative to which resources shall be loaded + * @return The object, or null if not supported (?) + */ + public static FbxObject createObject(FbxElement element, AssetManager assetManager, String sceneFolderName) { + String elementName = element.id; + String subclassName; + + if (element.propertiesTypes.length == 3) { + // FBX 7.x (all objects start with Long ID) + subclassName = (String) element.properties.get(2); + } else if (element.propertiesTypes.length == 2) { + // FBX 6.x (objects only have name and subclass) + subclassName = (String) element.properties.get(1); + } else { + // Not an object or invalid data. + return null; + } + + Class javaFbxClass = getImplementingClass(elementName, subclassName); + + if (javaFbxClass != null) { + try { + // This object is supported by FBX importer, create new instance. + // Import the data into the object from the element, then return it. + Constructor ctor = javaFbxClass.getConstructor(AssetManager.class, String.class); + FbxObject obj = ctor.newInstance(assetManager, sceneFolderName); + obj.fromElement(element); + + String subClassName = elementName + ", " + subclassName; + if (obj.assetManager == null) { + throw new IllegalStateException("FBXObject subclass (" + subClassName + + ") forgot to call super() in their constructor"); + } else if (obj.className == null) { + throw new IllegalStateException("FBXObject subclass (" + subClassName + + ") forgot to call super.fromElement() in their fromElement() implementation"); + } + return obj; + } catch (InvocationTargetException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } catch (NoSuchMethodException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } catch (InstantiationException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } catch (IllegalAccessException ex) { + // Programmer error. + throw new IllegalStateException(ex); + } + } + + // Not supported object. + return null; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java new file mode 100644 index 000000000..9a1eaa910 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java @@ -0,0 +1,54 @@ +/* + * 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.obj; + +import com.jme3.asset.AssetManager; + +public class FbxUnknownObject extends FbxObject { + + public FbxUnknownObject(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected Void toJmeObject() { + throw new UnsupportedOperationException(); + } + + @Override + public void connectObject(FbxObject object) { + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java new file mode 100644 index 000000000..523993f51 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java @@ -0,0 +1,89 @@ +/* + * 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; + +public class IrBoneWeightIndex implements Cloneable, Comparable { + + int boneIndex; + float boneWeight; + + public IrBoneWeightIndex(int boneIndex, float boneWeight) { + this.boneIndex = boneIndex; + this.boneWeight = boneWeight; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(ex); + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + this.boneIndex; + hash = 23 * hash + Float.floatToIntBits(this.boneWeight); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IrBoneWeightIndex other = (IrBoneWeightIndex) obj; + if (this.boneIndex != other.boneIndex) { + return false; + } + if (Float.floatToIntBits(this.boneWeight) != Float.floatToIntBits(other.boneWeight)) { + return false; + } + return true; + } + + @Override + public int compareTo(IrBoneWeightIndex o) { + if (boneWeight < o.boneWeight) { + return 1; + } else if (boneWeight > o.boneWeight) { + return -1; + } else { + return 0; + } + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java new file mode 100644 index 000000000..8bb5a6881 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java @@ -0,0 +1,46 @@ +/* + * 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; + +public class IrMesh { + + public IrPolygon[] polygons; + + public IrMesh deepClone() { + IrMesh m = new IrMesh(); + m.polygons = new IrPolygon[polygons.length]; + for (int i = 0; i < polygons.length; i++) { + m.polygons[i] = polygons[i].deepClone(); + } + return m; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java new file mode 100644 index 000000000..7bb47cb54 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java @@ -0,0 +1,46 @@ +/* + * 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; + +public class IrPolygon { + + public IrVertex[] vertices; + + public IrPolygon deepClone() { + IrPolygon p = new IrPolygon(); + p.vertices = new IrVertex[vertices.length]; + for (int i = 0; i < vertices.length; i++) { + p.vertices[i] = vertices[i].deepClone(); + } + return p; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java new file mode 100644 index 000000000..7b20e1670 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java @@ -0,0 +1,400 @@ +/* + * 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; + +import com.jme3.math.Vector4f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.mesh.IndexIntBuffer; +import com.jme3.scene.mesh.IndexShortBuffer; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class IrUtils { + + private static final Logger logger = Logger.getLogger(IrUtils.class.getName()); + + private IrUtils() { } + + private static IrPolygon[] quadToTri(IrPolygon quad) { + if (quad.vertices.length == 3) { + throw new IllegalStateException("Already a triangle"); + } + + IrPolygon[] t = new IrPolygon[]{ new IrPolygon(), new IrPolygon() }; + t[0].vertices = new IrVertex[3]; + t[1].vertices = new IrVertex[3]; + + IrVertex v0 = quad.vertices[0]; + IrVertex v1 = quad.vertices[1]; + IrVertex v2 = quad.vertices[2]; + IrVertex v3 = quad.vertices[3]; + + // find the pair of verticies that is closest to each over + // v0 and v2 + // OR + // v1 and v3 + float d1 = v0.pos.distanceSquared(v2.pos); + float d2 = v1.pos.distanceSquared(v3.pos); + if (d1 < d2) { + // v0 is close to v2 + // put an edge in v0, v2 + t[0].vertices[0] = v0; + t[0].vertices[1] = v1; + t[0].vertices[2] = v3; + + t[1].vertices[0] = v1; + t[1].vertices[1] = v2; + t[1].vertices[2] = v3; + } else { + // put an edge in v1, v3 + t[0].vertices[0] = v0; + t[0].vertices[1] = v1; + t[0].vertices[2] = v2; + + t[1].vertices[0] = v0; + t[1].vertices[1] = v2; + t[1].vertices[2] = v3; + } + + return t; + } + + /** + * Applies smoothing groups to vertex normals. + */ + public static IrMesh applySmoothingGroups(IrMesh mesh) { + return null; + } + + private static void toTangentsWithParity(IrVertex vertex) { + if (vertex.tang != null && vertex.bitang != null) { + float wCoord = vertex.norm.cross(vertex.tang).dot(vertex.bitang) < 0f ? -1f : 1f; + vertex.tang4d = new Vector4f(vertex.tang.x, vertex.tang.y, vertex.tang.z, wCoord); + vertex.tang = null; + vertex.bitang = null; + } + } + + public static void toTangentsWithParity(IrMesh mesh) { + for (IrPolygon polygon : mesh.polygons) { + for (IrVertex vertex : polygon.vertices) { + toTangentsWithParity(vertex); + } + } + } + + private static void trimBoneWeights(IrVertex vertex) { + if (vertex.boneWeightsIndices == null) { + return; + } + + IrBoneWeightIndex[] boneWeightsIndices = vertex.boneWeightsIndices; + + if (boneWeightsIndices.length <= 4) { + return; + } + + // Sort by weight + boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, boneWeightsIndices.length); + Arrays.sort(boneWeightsIndices); + + // Trim to four weights at most + boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, 4); + + // Renormalize weights + float sum = 0; + + for (int i = 0; i < boneWeightsIndices.length; i++) { + sum += boneWeightsIndices[i].boneWeight; + } + + if (sum != 1f) { + float sumToB = sum == 0 ? 0 : 1f / sum; + for (int i = 0; i < boneWeightsIndices.length; i++) { + IrBoneWeightIndex original = boneWeightsIndices[i]; + boneWeightsIndices[i] = new IrBoneWeightIndex(original.boneIndex, original.boneWeight * sumToB); + } + } + + vertex.boneWeightsIndices = boneWeightsIndices; + } + + /** + * Removes low bone weights from mesh, leaving only 4 bone weights at max. + */ + public static void trimBoneWeights(IrMesh mesh) { + for (IrPolygon polygon : mesh.polygons) { + for (IrVertex vertex : polygon.vertices) { + trimBoneWeights(vertex); + } + } + } + + /** + * Convert mesh from quads / triangles to triangles only. + */ + public static void triangulate(IrMesh mesh) { + List newPolygons = new ArrayList(mesh.polygons.length); + for (IrPolygon inputPoly : mesh.polygons) { + if (inputPoly.vertices.length == 4) { + IrPolygon[] tris = quadToTri(inputPoly); + newPolygons.add(tris[0]); + newPolygons.add(tris[1]); + } else if (inputPoly.vertices.length == 3) { + newPolygons.add(inputPoly); + } else { + // N-gon. We have to ignore it.. + logger.log(Level.WARNING, "N-gon encountered, ignoring. " + + "The mesh may not appear correctly. " + + "Triangulate your model prior to export."); + } + } + mesh.polygons = new IrPolygon[newPolygons.size()]; + newPolygons.toArray(mesh.polygons); + } + + /** + * Separate mesh with multiple materials into multiple meshes each with + * one material each. + * + * Polygons without a material will be added to key = -1. + */ + public static IntMap splitByMaterial(IrMesh mesh) { + IntMap> materialToPolyList = new IntMap>(); + for (IrPolygon polygon : mesh.polygons) { + int materialIndex = -1; + for (IrVertex vertex : polygon.vertices) { + if (vertex.material == null) { + continue; + } + if (materialIndex == -1) { + materialIndex = vertex.material; + } else if (materialIndex != vertex.material) { + throw new UnsupportedOperationException("Multiple materials " + + "assigned to the same polygon"); + } + } + List polyList = materialToPolyList.get(materialIndex); + if (polyList == null) { + polyList = new ArrayList(); + materialToPolyList.put(materialIndex, polyList); + } + polyList.add(polygon); + } + IntMap materialToMesh = new IntMap(); + for (IntMap.Entry> entry : materialToPolyList) { + int key = entry.getKey(); + List polygons = entry.getValue(); + if (polygons.size() > 0) { + IrMesh newMesh = new IrMesh(); + newMesh.polygons = new IrPolygon[polygons.size()]; + polygons.toArray(newMesh.polygons); + materialToMesh.put(key, newMesh); + } + } + return materialToMesh; + } + + /** + * Convert IrMesh to jME3 mesh. + */ + public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) { + Map vertexToVertexIndex = new HashMap(); + List vertices = new ArrayList(); + List indexes = new ArrayList(); + + int vertexIndex = 0; + for (IrPolygon polygon : mesh.polygons) { + if (polygon.vertices.length != 3) { + throw new UnsupportedOperationException("IrMesh must be triangulated first"); + } + for (IrVertex vertex : polygon.vertices) { + // Is this vertex already indexed? + Integer existingIndex = vertexToVertexIndex.get(vertex); + if (existingIndex == null) { + // Not indexed yet, allocate index. + indexes.add(vertexIndex); + vertexToVertexIndex.put(vertex, vertexIndex); + vertices.add(vertex); + vertexIndex++; + } else { + // Index already allocated for this vertex, reuse it. + indexes.add(existingIndex); + } + } + } + + Mesh jmeMesh = new Mesh(); + jmeMesh.setMode(Mesh.Mode.Triangles); + + FloatBuffer posBuf = null; + FloatBuffer normBuf = null; + FloatBuffer tangBuf = null; + FloatBuffer uv0Buf = null; + FloatBuffer uv1Buf = null; + ByteBuffer colorBuf = null; + ByteBuffer boneIndices = null; + FloatBuffer boneWeights = null; + IndexBuffer indexBuf = null; + + IrVertex inspectionVertex = vertices.get(0); + if (inspectionVertex.pos != null) { + posBuf = BufferUtils.createVector3Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); + } + if (inspectionVertex.norm != null) { + normBuf = BufferUtils.createVector3Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); + } + if (inspectionVertex.tang4d != null) { + tangBuf = BufferUtils.createFloatBuffer(vertices.size() * 4); + jmeMesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangBuf); + } + if (inspectionVertex.tang != null || inspectionVertex.bitang != null) { + throw new IllegalStateException("Mesh is using 3D tangents, must be converted to 4D tangents first."); + } + if (inspectionVertex.uv0 != null) { + uv0Buf = BufferUtils.createVector2Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv0Buf); + } + if (inspectionVertex.uv1 != null) { + uv1Buf = BufferUtils.createVector2Buffer(vertices.size()); + jmeMesh.setBuffer(VertexBuffer.Type.TexCoord2, 2, uv1Buf); + } + if (inspectionVertex.color != null) { + colorBuf = BufferUtils.createByteBuffer(vertices.size() * 4); + jmeMesh.setBuffer(VertexBuffer.Type.Color, 4, colorBuf); + jmeMesh.getBuffer(VertexBuffer.Type.Color).setNormalized(true); + } + if (inspectionVertex.boneWeightsIndices != null) { + boneIndices = BufferUtils.createByteBuffer(vertices.size() * 4); + boneWeights = BufferUtils.createFloatBuffer(vertices.size() * 4); + jmeMesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndices); + jmeMesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeights); + + //creating empty buffers for HW skinning + //the buffers will be setup if ever used. + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + //setting usage to cpuOnly so that the buffer is not send empty to the GPU + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + + jmeMesh.setBuffer(weightsHW); + jmeMesh.setBuffer(indicesHW); + } + if (vertices.size() >= 65536) { + // too many verticies: use intbuffer instead of shortbuffer + IntBuffer ib = BufferUtils.createIntBuffer(indexes.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, ib); + indexBuf = new IndexIntBuffer(ib); + } else { + ShortBuffer sb = BufferUtils.createShortBuffer(indexes.size()); + jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, sb); + indexBuf = new IndexShortBuffer(sb); + } + + jmeMesh.setStatic(); + + int maxBonesPerVertex = -1; + + for (IrVertex vertex : vertices) { + if (posBuf != null) { + posBuf.put(vertex.pos.x).put(vertex.pos.y).put(vertex.pos.z); + } + if (normBuf != null) { + normBuf.put(vertex.norm.x).put(vertex.norm.y).put(vertex.norm.z); + } + if (tangBuf != null) { + tangBuf.put(vertex.tang4d.x).put(vertex.tang4d.y).put(vertex.tang4d.z).put(vertex.tang4d.w); + } + if (uv0Buf != null) { + uv0Buf.put(vertex.uv0.x).put(vertex.uv0.y); + } + if (uv1Buf != null) { + uv1Buf.put(vertex.uv1.x).put(vertex.uv1.y); + } + if (colorBuf != null) { + colorBuf.putInt(vertex.color.asIntABGR()); + } + if (boneIndices != null) { + if (vertex.boneWeightsIndices != null) { + if (vertex.boneWeightsIndices.length > 4) { + throw new UnsupportedOperationException("Mesh uses more than 4 weights per bone. " + + "Call trimBoneWeights() to allieviate this"); + } + for (int i = 0; i < vertex.boneWeightsIndices.length; i++) { + boneIndices.put((byte) (vertex.boneWeightsIndices[i].boneIndex & 0xFF)); + boneWeights.put(vertex.boneWeightsIndices[i].boneWeight); + } + for (int i = 0; i < 4 - vertex.boneWeightsIndices.length; i++) { + boneIndices.put((byte)0); + boneWeights.put(0f); + } + } else { + boneIndices.putInt(0); + boneWeights.put(0f).put(0f).put(0f).put(0f); + } + + maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length); + } + } + + for (int i = 0; i < indexes.size(); i++) { + indexBuf.put(i, indexes.get(i)); + } + + jmeMesh.updateCounts(); + jmeMesh.updateBound(); + + if (boneIndices != null) { + jmeMesh.setMaxNumWeights(maxBonesPerVertex); + jmeMesh.prepareForAnim(true); + jmeMesh.generateBindPose(true); + } + + return jmeMesh; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java new file mode 100644 index 000000000..5870846a1 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java @@ -0,0 +1,170 @@ +/* + * 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; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import java.util.Arrays; + +public class IrVertex implements Cloneable { + + public Vector3f pos; + public Vector3f norm; + public Vector4f tang4d; + public Vector3f tang; + public Vector3f bitang; + public Vector2f uv0; + public Vector2f uv1; + public ColorRGBA color; + public Integer material; + public Integer smoothing; + public IrBoneWeightIndex[] boneWeightsIndices; + + public IrVertex deepClone() { + IrVertex v = new IrVertex(); + v.pos = pos != null ? pos.clone() : null; + v.norm = norm != null ? norm.clone() : null; + v.tang4d = tang4d != null ? tang4d.clone() : null; + v.tang = tang != null ? tang.clone() : null; + v.bitang = bitang != null ? bitang.clone() : null; + v.uv0 = uv0 != null ? uv0.clone() : null; + v.uv1 = uv1 != null ? uv1.clone() : null; + v.color = color != null ? color.clone() : null; + v.material = material; + v.smoothing = smoothing; + if (boneWeightsIndices != null) { + v.boneWeightsIndices = new IrBoneWeightIndex[boneWeightsIndices.length]; + for (int i = 0; i < boneWeightsIndices.length; i++) { + v.boneWeightsIndices[i] = (IrBoneWeightIndex) boneWeightsIndices[i].clone(); + } + } + return v; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 73 * hash + (this.pos != null ? this.pos.hashCode() : 0); + hash = 73 * hash + (this.norm != null ? this.norm.hashCode() : 0); + hash = 73 * hash + (this.tang4d != null ? this.tang4d.hashCode() : 0); + hash = 73 * hash + (this.tang != null ? this.tang.hashCode() : 0); + hash = 73 * hash + (this.uv0 != null ? this.uv0.hashCode() : 0); + hash = 73 * hash + (this.uv1 != null ? this.uv1.hashCode() : 0); + hash = 73 * hash + (this.color != null ? this.color.hashCode() : 0); + hash = 73 * hash + (this.material != null ? this.material.hashCode() : 0); + hash = 73 * hash + (this.smoothing != null ? this.smoothing.hashCode() : 0); + hash = 73 * hash + Arrays.deepHashCode(this.boneWeightsIndices); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IrVertex other = (IrVertex) obj; + if (this.pos != other.pos && (this.pos == null || !this.pos.equals(other.pos))) { + return false; + } + if (this.norm != other.norm && (this.norm == null || !this.norm.equals(other.norm))) { + return false; + } + if (this.tang4d != other.tang4d && (this.tang4d == null || !this.tang4d.equals(other.tang4d))) { + return false; + } + if (this.tang != other.tang && (this.tang == null || !this.tang.equals(other.tang))) { + return false; + } + if (this.uv0 != other.uv0 && (this.uv0 == null || !this.uv0.equals(other.uv0))) { + return false; + } + if (this.uv1 != other.uv1 && (this.uv1 == null || !this.uv1.equals(other.uv1))) { + return false; + } + if (this.color != other.color && (this.color == null || !this.color.equals(other.color))) { + return false; + } + if (this.material != other.material && (this.material == null || !this.material.equals(other.material))) { + return false; + } + if (this.smoothing != other.smoothing && (this.smoothing == null || !this.smoothing.equals(other.smoothing))) { + return false; + } + if (!Arrays.deepEquals(this.boneWeightsIndices, other.boneWeightsIndices)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Vertex { "); + + if (pos != null) { + sb.append("pos=").append(pos).append(", "); + } + if (norm != null) { + sb.append("norm=").append(pos).append(", "); + } + if (tang != null) { + sb.append("tang=").append(pos).append(", "); + } + if (uv0 != null) { + sb.append("uv0=").append(pos).append(", "); + } + if (uv1 != null) { + sb.append("uv1=").append(pos).append(", "); + } + if (color != null) { + sb.append("color=").append(pos).append(", "); + } + if (material != null) { + sb.append("material=").append(pos).append(", "); + } + if (smoothing != null) { + sb.append("smoothing=").append(pos).append(", "); + } + + if (sb.toString().endsWith(", ")) { + sb.delete(sb.length() - 2, sb.length()); + } + + sb.append(" }"); + return sb.toString(); + } +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java index d2767781c..461f76c0c 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java @@ -285,7 +285,7 @@ public abstract class SceneEditTool { * what part of the axis was selected. * For example if (1,0,0) is returned, then the X-axis pole was selected. * If (0,1,1) is returned, then the Y-Z plane was selected. - * + * * @return null if it did not intersect the marker */ protected Vector3f pickAxisMarker(Camera cam, Vector2f mouseLoc, AxisMarkerPickType pickType) { @@ -336,7 +336,7 @@ public abstract class SceneEditTool { CollisionResults results = new CollisionResults(); Ray ray = new Ray(); Vector3f pos = cam.getWorldCoordinates(mouseLoc, 0).clone(); - Vector3f dir = cam.getWorldCoordinates(mouseLoc, 0.1f).clone(); + Vector3f dir = cam.getWorldCoordinates(mouseLoc, 0.125f).clone(); dir.subtractLocal(pos).normalizeLocal(); ray.setOrigin(pos); ray.setDirection(dir); @@ -347,7 +347,7 @@ public abstract class SceneEditTool { /** * Show what axis or plane the mouse is currently over and will affect. - * @param axisMarkerPickType + * @param axisMarkerPickType */ protected void highlightAxisMarker(Camera camera, Vector2f screenCoord, AxisMarkerPickType axisMarkerPickType) { highlightAxisMarker(camera, screenCoord, axisMarkerPickType, false); @@ -355,12 +355,12 @@ public abstract class SceneEditTool { /** * Show what axis or plane the mouse is currently over and will affect. - * @param axisMarkerPickType + * @param axisMarkerPickType * @param colorAll highlight all parts of the marker when only one is selected */ protected void highlightAxisMarker(Camera camera, Vector2f screenCoord, AxisMarkerPickType axisMarkerPickType, boolean colorAll) { setDefaultAxisMarkerColors(); - Vector3f picked = pickAxisMarker(camera, screenCoord, axisPickType); + Vector3f picked = pickAxisMarker(camera, screenCoord, axisMarkerPickType); if (picked == null) { return; } @@ -453,6 +453,7 @@ public abstract class SceneEditTool { // axis.attachChild(quadYZ); axis.setModelBound(new BoundingBox()); + axis.updateModelBound(); return axis; } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java index 97f48cc9f..478a0f7c6 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveTool.java @@ -27,7 +27,8 @@ import org.openide.util.Lookup; */ public class MoveTool extends SceneEditTool { - private Vector3f pickedPlane; + private Vector3f pickedMarker; + private Vector3f constraintAxis; //used for one axis move private boolean wasDragging = false; private MoveManager moveManager; @@ -48,11 +49,12 @@ public class MoveTool extends SceneEditTool { public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint if (wasDragging) { actionPerformed(moveManager.makeUndo()); wasDragging = false; - } + } moveManager.reset(); } } @@ -63,20 +65,21 @@ public class MoveTool extends SceneEditTool { @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { - - if (pickedPlane == null) { + + if (pickedMarker == null) { highlightAxisMarker(camera, screenCoord, axisPickType); } else { - pickedPlane = null; + pickedMarker = null; moveManager.reset(); } } @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - if (!pressed) { + if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint if (wasDragging) { actionPerformed(moveManager.makeUndo()); wasDragging = false; @@ -89,21 +92,30 @@ public class MoveTool extends SceneEditTool { return; } - if (pickedPlane == null) { - pickedPlane = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedPlane == null) { + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { return; } - if (pickedPlane.equals(new Vector3f(1, 1, 0))) { + if (pickedMarker.equals(QUAD_XY)) { moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XY, true); - } else if (pickedPlane.equals(new Vector3f(1, 0, 1))) { + } else if (pickedMarker.equals(QUAD_XZ)) { moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XZ, true); - } else if (pickedPlane.equals(new Vector3f(0, 1, 1))) { + } else if (pickedMarker.equals(QUAD_YZ)) { moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.YZ, true); + } else if (pickedMarker.equals(ARROW_X)) { + moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XY, true); + constraintAxis = Vector3f.UNIT_X; // move only X + } else if (pickedMarker.equals(ARROW_Y)) { + moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.YZ, true); + constraintAxis = Vector3f.UNIT_Y; // move only Y + } else if (pickedMarker.equals(ARROW_Z)) { + moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XZ, true); + constraintAxis = Vector3f.UNIT_Z; // move only Z } } - if (!moveManager.move(camera, screenCoord)) { + if (!moveManager.move(camera, screenCoord, constraintAxis, false)) { return; } updateToolsTransformation(); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java index 862ef2be2..cfac57ae1 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/ScaleTool.java @@ -21,107 +21,118 @@ import org.openide.loaders.DataObject; * @author sploreg */ public class ScaleTool extends SceneEditTool { - - private Vector3f pickedPlane; + + private Vector3f pickedMarker; + private Vector3f constraintAxis; //used for one axis scale private Vector2f lastScreenCoord; private Vector3f startScale; private Vector3f lastScale; private boolean wasDragging = false; - + public ScaleTool() { axisPickType = AxisMarkerPickType.axisAndPlane; setOverrideCameraControl(true); } - + @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); displayPlanes(); } - + @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection lastScreenCoord = null; + constraintAxis = Vector3f.UNIT_XYZ; // no axis constraint if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startScale, lastScale)); wasDragging = false; } } } - + @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - + } @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { - if (pickedPlane == null) { + if (pickedMarker == null) { highlightAxisMarker(camera, screenCoord, axisPickType, true); } /*else { - pickedPlane = null; - lastScreenCoord = null; - }*/ + pickedPlane = null; + lastScreenCoord = null; + }*/ } @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { if (!pressed) { setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection + pickedMarker = null; // mouse released, reset selection lastScreenCoord = null; - + constraintAxis = Vector3f.UNIT_XYZ; // no axis constraint if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startScale, lastScale)); wasDragging = false; } return; } - - if (toolController.getSelectedSpatial() == null) + + if (toolController.getSelectedSpatial() == null) { return; - if (pickedPlane == null) { - pickedPlane = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedPlane == null) + } + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { return; + } + if (pickedMarker.equals(ARROW_X)) { + constraintAxis = Vector3f.UNIT_X; // scale only X + } else if (pickedMarker.equals(ARROW_Y)) { + constraintAxis = Vector3f.UNIT_Y; // scale only Y + } else if (pickedMarker.equals(ARROW_Z)) { + constraintAxis = Vector3f.UNIT_Z; // scale only Z + } startScale = toolController.getSelectedSpatial().getLocalScale().clone(); } - + if (lastScreenCoord == null) { lastScreenCoord = screenCoord; } else { - float diff = screenCoord.y-lastScreenCoord.y; + float diff = screenCoord.y - lastScreenCoord.y; diff *= 0.1f; lastScreenCoord = screenCoord; - Vector3f scale = toolController.getSelectedSpatial().getLocalScale().add(diff, diff, diff); + Vector3f scale = toolController.getSelectedSpatial().getLocalScale().add(new Vector3f(diff, diff, diff).multLocal(constraintAxis)); lastScale = scale; toolController.getSelectedSpatial().setLocalScale(scale); updateToolsTransformation(); } - + wasDragging = true; } @Override public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - + } private class ScaleUndo extends AbstractUndoableSceneEdit { private Spatial spatial; - private Vector3f before,after; - + private Vector3f before, after; + ScaleUndo(Spatial spatial, Vector3f before, Vector3f after) { this.spatial = spatial; this.before = before; this.after = after; } - + @Override public void sceneUndo() { spatial.setLocalScale(before); @@ -133,6 +144,6 @@ public class ScaleTool extends SceneEditTool { spatial.setLocalScale(after); toolController.selectedSpatialTransformed(); } - + } }