From 39515c52c75ff304b97705530072181aaf123dbb Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 18 Mar 2015 22:12:20 +0100 Subject: [PATCH 001/225] Relocated the character in TestWalkingChar so that it doesn't fall under the ground --- .../src/main/java/jme3test/bullet/TestWalkingChar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java index 6e793da61..a66a6ecc1 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java @@ -297,7 +297,7 @@ public class TestWalkingChar extends SimpleApplication implements ActionListener model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); //model.setLocalScale(0.5f); model.addControl(character); - character.setPhysicsLocation(new Vector3f(-140, 15, -10)); + character.setPhysicsLocation(new Vector3f(-140, 40, -10)); rootNode.attachChild(model); getPhysicsSpace().add(character); } From 080255f51860d6803b88c0c91206a0c6c42da5f4 Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 18 Mar 2015 22:30:07 +0100 Subject: [PATCH 002/225] Fixed issue #243 about TextureArrays --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 2 -- 1 file changed, 2 deletions(-) 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 395b7e485..d1cc6338c 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 @@ -2089,9 +2089,7 @@ public class GLRenderer implements Renderer { if (caps.contains(Caps.OpenGL30) || gl2 == null) { if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired() && img.getData(0) != null) { - if (gl2 != null) gl2.glEnable(target); // XXX: Required for ATI glfbo.glGenerateMipmapEXT(target); - if (gl2 != null) gl2.glDisable(target); img.setMipmapsGenerated(true); } } From 96502e9061ae83130998626abd78d15c97c87d20 Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 18 Mar 2015 23:51:02 +0100 Subject: [PATCH 003/225] Fixed crash in TestFilterCompositing. Something is till odd though --- .../post/TestPostFiltersCompositing.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java b/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java index f05276a81..81f9a3a28 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java @@ -32,16 +32,16 @@ package jme3test.post; import com.jme3.app.SimpleApplication; -import com.jme3.asset.plugins.ZipLocator; +import com.jme3.asset.plugins.HttpZipLocator; import com.jme3.light.DirectionalLight; import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.post.FilterPostProcessor; -import com.jme3.post.filters.BloomFilter; import com.jme3.post.filters.ColorOverlayFilter; import com.jme3.post.filters.ComposeFilter; import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.texture.Texture2D; @@ -55,8 +55,12 @@ import com.jme3.util.SkyFactory; public class TestPostFiltersCompositing extends SimpleApplication { public static void main(String[] args) { - TestPostFiltersCompositing app = new TestPostFiltersCompositing(); - app.start(); + TestPostFiltersCompositing app = new TestPostFiltersCompositing(); + AppSettings settings = new AppSettings(true); + settings.putBoolean("GraphicsDebug", false); + app.setSettings(settings); + app.start(); + } public void simpleInitApp() { @@ -76,7 +80,7 @@ public class TestPostFiltersCompositing extends SimpleApplication { Texture2D mainVPTexture = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); mainVPFrameBuffer.addColorTexture(mainVPTexture); mainVPFrameBuffer.setDepthBuffer(Image.Format.Depth); - viewPort.setOutputFrameBuffer(mainVPFrameBuffer); + viewPort.setOutputFrameBuffer(mainVPFrameBuffer); //creating the post processor for the gui viewport final FilterPostProcessor guifpp = new FilterPostProcessor(assetManager); @@ -87,17 +91,18 @@ public class TestPostFiltersCompositing extends SimpleApplication { guiViewPort.addProcessor(guifpp); - //compositing is done my mixing texture depending on the alpha channel, + //compositing is done by mixing texture depending on the alpha channel, //it's important that the guiviewport clear color alpha value is set to 0 - guiViewPort.setBackgroundColor(new ColorRGBA(0, 0, 0, 0)); + guiViewPort.setBackgroundColor(ColorRGBA.BlackNoAlpha); + guiViewPort.setClearColor(true); } private void makeScene() { // load sky - rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); - assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap)); + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class); Spatial scene = assetManager.loadModel("main.scene"); DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(-0.4790551f, -0.39247334f, -0.7851566f)); From 6d7da0cc4e139495dbe90cc16f5f5bc52e5c0c04 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:11:39 -0400 Subject: [PATCH 004/225] AssetManager: use right exception class if no loaders registered --- jme3-core/src/main/java/com/jme3/asset/ImplHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java index 571be53c3..3601bc463 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java +++ b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java @@ -195,8 +195,8 @@ public class ImplHandler { // No need to synchronize() against map, its concurrent ImplThreadLocal local = extensionToLoaderMap.get(key.getExtension()); if (local == null){ - throw new IllegalStateException("No loader registered for type \"" + - key.getExtension() + "\""); + throw new AssetLoadException("No loader registered for type \"" + + key.getExtension() + "\""); } return (AssetLoader) local.get(); From c34fcce7a20981ad48c6a3fd093a6c3cef2c475b Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:13:09 -0400 Subject: [PATCH 005/225] MatParamTexture: remove useless constructor --- .../src/main/java/com/jme3/material/MatParamTexture.java | 6 ------ jme3-core/src/main/java/com/jme3/material/Material.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java index b24d27a95..a3db63a1c 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java @@ -47,12 +47,6 @@ public class MatParamTexture extends MatParam { private int unit; private ColorSpace colorSpace; - public MatParamTexture(VarType type, String name, Texture texture, int unit) { - super(type, name, texture); - this.texture = texture; - this.unit = unit; - } - public MatParamTexture(VarType type, String name, Texture texture, int unit, ColorSpace colorSpace) { super(type, name, texture); this.texture = texture; diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 74fdbb006..5f0ab8f91 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -556,7 +556,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { + "Linear using texture.getImage.setColorSpace().", new Object[]{value.getName(), value.getImage().getColorSpace().name(), name}); } - paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++)); + paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++, null)); } else { val.setTextureValue(value); } From 784cfddb2113105e317d8bdd6be0bf3d7e8fa271 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:14:01 -0400 Subject: [PATCH 006/225] Filter: minor javadoc mistake --- jme3-core/src/main/java/com/jme3/post/Filter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java index 320ff734d..a6e2e3c01 100644 --- a/jme3-core/src/main/java/com/jme3/post/Filter.java +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -201,7 +201,7 @@ public abstract class Filter implements Savable { /** * returns the default pass texture format - * default is {@link Format#RGB10_A2} + * default is {@link Format#RGB111110F} * * @return */ From 83ddf9d7c3f87ca352d67705d36744867a4c253c Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:15:11 -0400 Subject: [PATCH 007/225] TestCartoonEdge: more reliable way to detect lighting material --- jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java b/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java index 1ca328a8a..ff6ab497f 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java +++ b/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java @@ -80,7 +80,7 @@ public class TestCartoonEdge extends SimpleApplication { }else if (spatial instanceof Geometry){ Geometry g = (Geometry) spatial; Material m = g.getMaterial(); - if (m.getMaterialDef().getName().equals("Phong Lighting")){ + if (m.getMaterialDef().getMaterialParam("UseMaterialColors") != null) { Texture t = assetManager.loadTexture("Textures/ColorRamp/toon.png"); // t.setMinFilter(Texture.MinFilter.NearestNoMipMaps); // t.setMagFilter(Texture.MagFilter.Nearest); From c0e85b325545b27653db9228a4c7d97b29f51946 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:15:57 -0400 Subject: [PATCH 008/225] MaterialDebug: remove DesktopAssetManager dependency --- .../main/java/com/jme3/util/MaterialDebugAppState.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java index 1c512e41b..6197aca35 100644 --- a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java +++ b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java @@ -36,7 +36,7 @@ import com.jme3.app.state.AbstractAppState; import com.jme3.app.state.AppStateManager; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetKey; -import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.AssetManager; import com.jme3.asset.plugins.UrlAssetInfo; import com.jme3.input.InputManager; import com.jme3.input.controls.ActionListener; @@ -96,7 +96,7 @@ import java.util.logging.Logger; public class MaterialDebugAppState extends AbstractAppState { private RenderManager renderManager; - private DesktopAssetManager assetManager; + private AssetManager assetManager; private InputManager inputManager; private List bindings = new ArrayList(); private Map> fileTriggers = new HashMap> (); @@ -105,7 +105,7 @@ public class MaterialDebugAppState extends AbstractAppState { @Override public void initialize(AppStateManager stateManager, Application app) { renderManager = app.getRenderManager(); - assetManager = (DesktopAssetManager) app.getAssetManager(); + assetManager = app.getAssetManager(); inputManager = app.getInputManager(); for (Binding binding : bindings) { bind(binding); @@ -197,7 +197,7 @@ public class MaterialDebugAppState extends AbstractAppState { public Material reloadMaterial(Material mat) { //clear the entire cache, there might be more clever things to do, like clearing only the matdef, and the associated shaders. - ((DesktopAssetManager) assetManager).clearCache(); + assetManager.clearCache(); //creating a dummy mat with the mat def of the mat to reload Material dummy = new Material(mat.getMaterialDef()); From d6a19c4c66d8ca9fcd96a99144a5594f9ba2f890 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:16:48 -0400 Subject: [PATCH 009/225] Lighting.j3md: remove fixed function bindings --- .../main/resources/Common/MatDefs/Light/Lighting.j3md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index df841ddde..0e9e9850b 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -7,7 +7,7 @@ MaterialDef Phong Lighting { Boolean VertexLighting // Alpha threshold for fragment discarding - Float AlphaDiscardThreshold (AlphaTestFallOff) + Float AlphaDiscardThreshold // Use the provided ambient, diffuse, and specular colors Boolean UseMaterialColors @@ -16,16 +16,16 @@ MaterialDef Phong Lighting { Boolean UseVertexColor // Ambient color - Color Ambient (MaterialAmbient) + Color Ambient // Diffuse color - Color Diffuse (MaterialDiffuse) + Color Diffuse // Specular color - Color Specular (MaterialSpecular) + Color Specular // Specular power/shininess - Float Shininess (MaterialShininess) : 1 + Float Shininess : 1 // Diffuse map Texture2D DiffuseMap From 3a83ab4c6924175e84184e2cc0715b22eacad8cc Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:19:43 -0400 Subject: [PATCH 010/225] GLRenderer: applied 080255f51860d6803b88c0c91206a0c6c42da5f4 to FrameBuffer textures as well --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 6 ------ 1 file changed, 6 deletions(-) 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 d1cc6338c..4e20c5de0 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 @@ -1542,13 +1542,7 @@ public class GLRenderer implements Renderer { setTexture(0, rb.getTexture()); int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); - if (gl2 != null) { - gl2.glEnable(textureType); - } glfbo.glGenerateMipmapEXT(textureType); - if (gl2 != null) { - gl2.glDisable(textureType); - } } } } From a157e83815521728db6fb0de405f2b00a9028bdf Mon Sep 17 00:00:00 2001 From: shadowislord Date: Wed, 18 Mar 2015 23:21:25 -0400 Subject: [PATCH 011/225] GLRenderer: always use glGenerateMipmap if we have FBO support --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4e20c5de0..9d080384a 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 @@ -1994,7 +1994,7 @@ public class GLRenderer implements Renderer { // Image does not have mipmaps, but they are required. // Generate from base level. - if (!caps.contains(Caps.OpenGL30) && gl2 != null) { + if (!caps.contains(Caps.FrameBuffer) && gl2 != null) { gl2.glTexParameteri(target, GL2.GL_GENERATE_MIPMAP, GL.GL_TRUE); img.setMipmapsGenerated(true); } else { @@ -2081,7 +2081,7 @@ public class GLRenderer implements Renderer { img.setMultiSamples(imageSamples); } - if (caps.contains(Caps.OpenGL30) || gl2 == null) { + if (caps.contains(Caps.FrameBuffer) || gl2 == null) { if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired() && img.getData(0) != null) { glfbo.glGenerateMipmapEXT(target); img.setMipmapsGenerated(true); From b7c2050487fe89458c98e012f502870dd1075adb Mon Sep 17 00:00:00 2001 From: shadowislord Date: Thu, 19 Mar 2015 22:48:01 -0400 Subject: [PATCH 012/225] SDK: fix compliation --- .../src/com/jme3/gde/materials/EditableMaterialFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/EditableMaterialFile.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/EditableMaterialFile.java index 4c718c7a2..9b5cf611c 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/EditableMaterialFile.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/EditableMaterialFile.java @@ -532,7 +532,7 @@ public class EditableMaterialFile { //TODO: seems like flip is removed due to ImageToAwt texKey.setFlipY(false); Texture texture = manager.loadTexture(texKey); - MatParamTexture newParam = new MatParamTexture(texParam.getVarType(), texParam.getName(), texture, texParam.getUnit()); + MatParamTexture newParam = new MatParamTexture(texParam.getVarType(), texParam.getName(), texture, texParam.getUnit(), null); materialParameters.put(newParam.getName(), new MaterialProperty(newParam)); } catch (Exception ex) { Exceptions.printStackTrace(ex); From a37f38a412bfe4472a9d295069fae863d34afd9f Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 20 Mar 2015 23:51:15 -0400 Subject: [PATCH 013/225] Build: fix build failure if not building from git repo --- jme3-core/build.gradle | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/jme3-core/build.gradle b/jme3-core/build.gradle index 78bc24dd1..bd699f52a 100644 --- a/jme3-core/build.gradle +++ b/jme3-core/build.gradle @@ -26,14 +26,29 @@ import org.ajoberstar.grgit.* task updateVersion << { - def grgit = Grgit.open(project.file('.').parent) + def verfile = file('src/main/java/com/jme3/system/JmeVersion.java') + def jmeGitHash + def jmeShortGitHash + def jmeBuildDate + def jmeBranchName - def jmeGitHash = grgit.head().id - def jmeShortGitHash = grgit.head().abbreviatedId - def jmeBuildDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date()) - def jmeBranchName = grgit.branch.current.name + try { + def grgit = Grgit.open(project.file('.').parent) + jmeGitHash = grgit.head().id + jmeShortGitHash = grgit.head().abbreviatedId + jmeBuildDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + jmeBranchName = grgit.branch.current.name + } catch (ex) { + // Failed to get repo info + logger.warn("Failed to get repository info: " + ex.message + ". " + \ + "Only partial build info will be generated.") + + jmeGitHash = "" + jmeShortGitHash = "" + jmeBuildDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + jmeBranchName = "unknown" + } - def verfile = file('src/main/java/com/jme3/system/JmeVersion.java') verfile.text = "\npackage com.jme3.system;\n\n" + "/**\n * THIS IS AN AUTO-GENERATED FILE..\n * DO NOT MODIFY!\n */\n" + "public class JmeVersion {\n" + From eb767e75802cab5ae20597fee7fb6fa40ac55d45 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Mon, 23 Mar 2015 20:09:45 +0100 Subject: [PATCH 014/225] Feature: added support for loading assets linked from external blender files. --- .../main/java/com/jme3/asset/BlenderKey.java | 312 +----------------- .../blender/AbstractBlenderHelper.java | 138 +++++++- .../scene/plugins/blender/BlenderContext.java | 91 +++-- .../scene/plugins/blender/BlenderLoader.java | 263 +++++++++------ .../plugins/blender/BlenderModelLoader.java | 71 +--- .../blender/animations/AnimationHelper.java | 3 +- .../plugins/blender/cameras/CameraHelper.java | 15 +- .../plugins/blender/file/FileBlockHeader.java | 87 ++--- .../scene/plugins/blender/file/Structure.java | 3 +- .../plugins/blender/lights/LightHelper.java | 8 +- .../blender/materials/MaterialContext.java | 25 +- .../blender/materials/MaterialHelper.java | 7 +- .../plugins/blender/meshes/MeshHelper.java | 13 +- .../plugins/blender/objects/ObjectHelper.java | 70 ++-- .../blender/textures/TextureHelper.java | 90 +++-- .../blending/AbstractTextureBlender.java | 18 +- .../blending/TextureBlenderFactory.java | 7 +- .../main/java/com/jme3/scene/UserData.java | 221 +++++++++++-- 18 files changed, 780 insertions(+), 662 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java index c215d0677..c412c07b3 100644 --- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java +++ b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java @@ -32,36 +32,19 @@ package com.jme3.asset; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import com.jme3.animation.Animation; -import com.jme3.bounding.BoundingVolume; -import com.jme3.collision.Collidable; -import com.jme3.collision.CollisionResults; -import com.jme3.collision.UnsupportedCollisionException; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.material.RenderState.FaceCullMode; -import com.jme3.math.ColorRGBA; -import com.jme3.post.Filter; -import com.jme3.scene.CameraNode; -import com.jme3.scene.LightNode; -import com.jme3.scene.Node; -import com.jme3.scene.SceneGraphVisitor; -import com.jme3.scene.Spatial; -import com.jme3.texture.Texture; /** * Blender key. Contains path of the blender file and its loading properties. * @author Marcin Roguski (Kaelthas) */ public class BlenderKey extends ModelKey { - protected static final int DEFAULT_FPS = 25; /** * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time @@ -72,7 +55,7 @@ public class BlenderKey extends ModelKey { * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. */ protected int featuresToLoad = FeaturesToLoad.ALL; - /** This variable determines if assets that are not linked to the objects should be loaded. */ + /** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */ protected boolean loadUnlinkedAssets; /** The root path for all the assets. */ protected String assetRootPath; @@ -268,6 +251,7 @@ public class BlenderKey extends ModelKey { * @param featuresToLoad * bitwise flag of FeaturesToLoad interface values */ + @Deprecated public void includeInLoading(int featuresToLoad) { this.featuresToLoad |= featuresToLoad; } @@ -277,10 +261,12 @@ public class BlenderKey extends ModelKey { * @param featuresNotToLoad * bitwise flag of FeaturesToLoad interface values */ + @Deprecated public void excludeFromLoading(int featuresNotToLoad) { featuresToLoad &= ~featuresNotToLoad; } + @Deprecated public boolean shouldLoad(int featureToLoad) { return (featuresToLoad & featureToLoad) != 0; } @@ -290,6 +276,7 @@ public class BlenderKey extends ModelKey { * the blender file loader. * @return features that will be loaded by the blender file loader */ + @Deprecated public int getFeaturesToLoad() { return featuresToLoad; } @@ -317,15 +304,6 @@ public class BlenderKey extends ModelKey { this.loadUnlinkedAssets = loadUnlinkedAssets; } - /** - * This method creates an object where loading results will be stores. Only those features will be allowed to store - * that were specified by features-to-load flag. - * @return an object to store loading results - */ - public LoadingResults prepareLoadingResults() { - return new LoadingResults(featuresToLoad); - } - /** * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y * is up axis. @@ -699,8 +677,11 @@ public class BlenderKey extends ModelKey { /** * This interface describes the features of the scene that are to be loaded. + * @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency + * everything must be loaded because in blender one feature might depend on another * @author Marcin Roguski (Kaelthas) */ + @Deprecated public static interface FeaturesToLoad { int SCENES = 0x0000FFFF; @@ -745,281 +726,4 @@ public class BlenderKey extends ModelKey { */ ALL_NAMES_MATCH; } - - /** - * This class holds the loading results according to the given loading flag. - * @author Marcin Roguski (Kaelthas) - */ - public static class LoadingResults extends Spatial { - - /** Bitwise mask of features that are to be loaded. */ - private final int featuresToLoad; - /** The scenes from the file. */ - private List scenes; - /** Objects from all scenes. */ - private List objects; - /** Materials from all objects. */ - private List materials; - /** Textures from all objects. */ - private List textures; - /** Animations of all objects. */ - private List animations; - /** All cameras from the file. */ - private List cameras; - /** All lights from the file. */ - private List lights; - /** Loaded sky. */ - private Spatial sky; - /** Scene filters (ie. FOG). */ - private List filters; - /** - * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color - * is set to default (as in blender editor. - */ - private ColorRGBA backgroundColor = ColorRGBA.Gray; - - /** - * Private constructor prevents users to create an instance of this class from outside the - * @param featuresToLoad - * bitwise mask of features that are to be loaded - * @see FeaturesToLoad FeaturesToLoad - */ - private LoadingResults(int featuresToLoad) { - this.featuresToLoad = featuresToLoad; - if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) { - scenes = new ArrayList(); - } - if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { - objects = new ArrayList(); - if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { - materials = new ArrayList(); - if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { - textures = new ArrayList(); - } - } - if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { - animations = new ArrayList(); - } - } - if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { - cameras = new ArrayList(); - } - if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { - lights = new ArrayList(); - } - } - - /** - * This method returns a bitwise flag describing what features of the blend file will be included in the result. - * @return bitwise mask of features that are to be loaded - * @see FeaturesToLoad FeaturesToLoad - */ - public int getLoadedFeatures() { - return featuresToLoad; - } - - /** - * This method adds a scene to the result set. - * @param scene - * scene to be added to the result set - */ - public void addScene(Node scene) { - if (scenes != null) { - scenes.add(scene); - } - } - - /** - * This method adds an object to the result set. - * @param object - * object to be added to the result set - */ - public void addObject(Node object) { - if (objects != null) { - objects.add(object); - } - } - - /** - * This method adds a material to the result set. - * @param material - * material to be added to the result set - */ - public void addMaterial(Material material) { - if (materials != null) { - materials.add(material); - } - } - - /** - * This method adds a texture to the result set. - * @param texture - * texture to be added to the result set - */ - public void addTexture(Texture texture) { - if (textures != null) { - textures.add(texture); - } - } - - /** - * This method adds a camera to the result set. - * @param camera - * camera to be added to the result set - */ - public void addCamera(CameraNode camera) { - if (cameras != null) { - cameras.add(camera); - } - } - - /** - * This method adds a light to the result set. - * @param light - * light to be added to the result set - */ - public void addLight(LightNode light) { - if (lights != null) { - lights.add(light); - } - } - - /** - * This method sets the sky of the scene. Only one sky can be set. - * @param sky - * the sky to be set - */ - public void setSky(Spatial sky) { - this.sky = sky; - } - - /** - * This method adds a scene filter. Filters are used to load FOG or other - * scene effects that blender can define. - * @param filter - * the filter to be added - */ - public void addFilter(Filter filter) { - if (filter != null) { - if (filters == null) { - filters = new ArrayList(5); - } - filters.add(filter); - } - } - - /** - * @param backgroundColor - * the background color - */ - public void setBackgroundColor(ColorRGBA backgroundColor) { - this.backgroundColor = backgroundColor; - } - - /** - * @return all loaded scenes - */ - public List getScenes() { - return scenes; - } - - /** - * @return all loaded objects - */ - public List getObjects() { - return objects; - } - - /** - * @return all loaded materials - */ - public List getMaterials() { - return materials; - } - - /** - * @return all loaded textures - */ - public List getTextures() { - return textures; - } - - /** - * @return all loaded animations - */ - public List getAnimations() { - return animations; - } - - /** - * @return all loaded cameras - */ - public List getCameras() { - return cameras; - } - - /** - * @return all loaded lights - */ - public List getLights() { - return lights; - } - - /** - * @return the scene's sky - */ - public Spatial getSky() { - return sky; - } - - /** - * @return scene filters - */ - public List getFilters() { - return filters; - } - - /** - * @return the background color - */ - public ColorRGBA getBackgroundColor() { - return backgroundColor; - } - - @Override - public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { - return 0; - } - - @Override - public void updateModelBound() { - } - - @Override - public void setModelBound(BoundingVolume modelBound) { - } - - @Override - public int getVertexCount() { - return 0; - } - - @Override - public int getTriangleCount() { - return 0; - } - - @Override - public Spatial deepClone() { - return null; - } - - @Override - public void depthFirstTraversal(SceneGraphVisitor visitor) { - } - - @Override - protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { - } - } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java index eff993f76..e11101c14 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java @@ -31,17 +31,34 @@ */ package com.jme3.scene.plugins.blender; +import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.jme3.animation.Animation; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.BlenderKey; import com.jme3.export.Savable; +import com.jme3.light.Light; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; +import com.jme3.scene.Node; import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.objects.Properties; +import com.jme3.texture.Texture; /** * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can @@ -49,14 +66,16 @@ import com.jme3.scene.plugins.blender.objects.Properties; * @author Marcin Roguski */ public abstract class AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName()); + /** The blender context. */ - protected BlenderContext blenderContext; + protected BlenderContext blenderContext; /** The version of the blend file. */ - protected final int blenderVersion; + protected final int blenderVersion; /** This variable indicates if the Y asxis is the UP axis or not. */ - protected boolean fixUpAxis; + protected boolean fixUpAxis; /** Quaternion used to rotate data when Y is up axis. */ - protected Quaternion upAxisRotationQuaternion; + protected Quaternion upAxisRotationQuaternion; /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender @@ -129,4 +148,115 @@ public abstract class AbstractBlenderHelper { } } } + + /** + * The method loads library of a given ID from linked blender file. + * @param id + * the ID of the linked feature (it contains its name and blender path) + * @return loaded feature or null if none was found + * @throws BlenderFileException + * and exception is throw when problems with reading a blend file occur + */ + @SuppressWarnings("unchecked") + protected Object loadLibrary(Structure id) throws BlenderFileException { + Pointer pLib = (Pointer) id.getFieldValue("lib"); + if (pLib.isNotNull()) { + String fullName = id.getFieldValue("name").toString();// we need full name with the prefix + String nameOfFeatureToLoad = id.getName(); + Structure library = pLib.fetchData().get(0); + String path = library.getFieldValue("filepath").toString(); + + if (!blenderContext.getLinkedFeatures().keySet().contains(path)) { + File file = new File(path); + List pathsToCheck = new ArrayList(); + String currentPath = file.getName(); + do { + pathsToCheck.add(currentPath); + file = file.getParentFile(); + if (file != null) { + currentPath = file.getName() + '/' + currentPath; + } + } while (file != null); + + Spatial loadedAsset = null; + BlenderKey blenderKey = null; + for (String p : pathsToCheck) { + blenderKey = new BlenderKey(p); + blenderKey.setLoadUnlinkedAssets(true); + try { + loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey); + break;// break if no exception was thrown + } catch (AssetNotFoundException e) { + LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", p); + } + } + + if (loadedAsset != null) { + Map> linkedData = loadedAsset.getUserData("linkedData"); + for (Entry> entry : linkedData.entrySet()) { + String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey(); + + List scenes = (List) entry.getValue().get("scenes"); + for (Node scene : scenes) { + blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene); + } + List objects = (List) entry.getValue().get("objects"); + for (Node object : objects) { + blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object); + } + List meshes = (List) entry.getValue().get("meshes"); + for (TemporalMesh mesh : meshes) { + blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh); + } + List materials = (List) entry.getValue().get("materials"); + for (MaterialContext materialContext : materials) { + blenderContext.addLinkedFeature(linkedDataFilePath, "MA" + materialContext.getName(), materialContext); + } + List textures = (List) entry.getValue().get("textures"); + for (Texture texture : textures) { + blenderContext.addLinkedFeature(linkedDataFilePath, "TE" + texture.getName(), texture); + } + List images = (List) entry.getValue().get("images"); + for (Texture image : images) { + blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image); + } + List animations = (List) entry.getValue().get("animations"); + for (Animation animation : animations) { + blenderContext.addLinkedFeature(linkedDataFilePath, "AC" + animation.getName(), animation); + } + List cameras = (List) entry.getValue().get("cameras"); + for (Camera camera : cameras) { + blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera); + } + List lights = (List) entry.getValue().get("lights"); + for (Light light : lights) { + blenderContext.addLinkedFeature(linkedDataFilePath, "LA" + light.getName(), light); + } + Spatial sky = (Spatial) entry.getValue().get("sky"); + if (sky != null) { + blenderContext.addLinkedFeature(linkedDataFilePath, sky.getName(), sky); + } + List filters = (List) entry.getValue().get("filters"); + for (Filter filter : filters) { + blenderContext.addLinkedFeature(linkedDataFilePath, filter.getName(), filter); + } + } + } else { + LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path); + } + } + + Object result = blenderContext.getLinkedFeature(path, fullName); + if (result == null) { + LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path }); + } else { + blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id); + blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result); + } + return result; + } else { + LOGGER.warning("Library link points to nothing!"); + } + return null; + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java index cde38e327..6e8042b09 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java @@ -53,6 +53,7 @@ import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.DnaBlockData; import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -64,49 +65,51 @@ import com.jme3.scene.plugins.blender.file.Structure; */ public class BlenderContext { /** The blender file version. */ - private int blenderVersion; + private int blenderVersion; /** The blender key. */ - private BlenderKey blenderKey; + private BlenderKey blenderKey; /** The header of the file block. */ - private DnaBlockData dnaBlockData; + private DnaBlockData dnaBlockData; /** The scene structure. */ - private Structure sceneStructure; + private Structure sceneStructure; /** The input stream of the blend file. */ - private BlenderInputStream inputStream; + private BlenderInputStream inputStream; /** The asset manager. */ - private AssetManager assetManager; + private AssetManager assetManager; /** The blocks read from the file. */ - protected List blocks; + protected List blocks; /** * A map containing the file block headers. The key is the old memory address. */ - private Map fileBlockHeadersByOma = new HashMap(); + private Map fileBlockHeadersByOma = new HashMap(); /** A map containing the file block headers. The key is the block code. */ - private Map> fileBlockHeadersByCode = new HashMap>(); + private Map> fileBlockHeadersByCode = new HashMap>(); /** * This map stores the loaded features by their old memory address. The * first object in the value table is the loaded structure and the second - * the structure already converted into proper data. */ - private Map> loadedFeatures = new HashMap>(); + private Map> loadedFeatures = new HashMap>(); + /** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */ + private Map> linkedFeatures = new HashMap>(); /** A stack that hold the parent structure of currently loaded feature. */ - private Stack parentStack = new Stack(); + private Stack parentStack = new Stack(); /** A list of constraints for the specified object. */ - protected Map> constraints = new HashMap>(); + protected Map> constraints = new HashMap>(); /** Animations loaded for features. */ - private Map> animations = new HashMap>(); + private Map> animations = new HashMap>(); /** Loaded skeletons. */ - private Map skeletons = new HashMap(); + private Map skeletons = new HashMap(); /** A map between skeleton and node it modifies. */ - private Map nodesWithSkeletons = new HashMap(); + private Map nodesWithSkeletons = new HashMap(); /** A map of bone contexts. */ - protected Map boneContexts = new HashMap(); + protected Map boneContexts = new HashMap(); /** A map og helpers that perform loading. */ - private Map helpers = new HashMap(); + private Map helpers = new HashMap(); /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */ - private Map> markers = new HashMap>(); + private Map> markers = new HashMap>(); /** A map of blender actions. The key is the action name and the value is the action itself. */ - private Map actions = new HashMap(); + private Map actions = new HashMap(); /** * This method sets the blender file version. @@ -231,10 +234,10 @@ public class BlenderContext { */ public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); - List headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); + List headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode()); if (headers == null) { headers = new ArrayList(); - fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); + fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers); } headers.add(fileBlockHeader); } @@ -258,7 +261,7 @@ public class BlenderContext { * the code of file blocks * @return a list of file blocks' headers of a specified code */ - public List getFileBlocks(Integer code) { + public List getFileBlocks(BlockCode code) { return fileBlockHeadersByCode.get(code); } @@ -299,7 +302,7 @@ public class BlenderContext { throw new IllegalArgumentException("One of the given arguments is null!"); } Map map = loadedFeatures.get(oldMemoryAddress); - if(map == null) { + if (map == null) { map = new HashMap(); loadedFeatures.put(oldMemoryAddress, map); } @@ -325,6 +328,48 @@ public class BlenderContext { return null; } + /** + * The method adds linked content to the blender context. + * @param blenderFilePath + * the path of linked blender file + * @param featureName + * the linked feature name + * @param feature + * the linked feature + */ + public void addLinkedFeature(String blenderFilePath, String featureName, Object feature) { + if (feature != null) { + Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); + if (linkedFeatures == null) { + linkedFeatures = new HashMap(); + this.linkedFeatures.put(blenderFilePath, linkedFeatures); + } + if (!linkedFeatures.containsKey(featureName)) { + linkedFeatures.put(featureName, feature); + } + } + } + + /** + * The method returns linked feature of a given name from the specified blender path. + * @param blenderFilePath + * the blender file path + * @param featureName + * the feature name we want to get + * @return linked feature or null if none was found + */ + public Object getLinkedFeature(String blenderFilePath, String featureName) { + Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); + return linkedFeatures != null ? linkedFeatures.get(featureName) : null; + } + + /** + * @return all linked features for the current blend file + */ + public Map> getLinkedFeatures() { + return linkedFeatures; + } + /** * This method adds the structure to the parent stack. * diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java index 200f83b39..dd0160063 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -33,17 +33,21 @@ package com.jme3.scene.plugins.blender; import java.io.IOException; 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; +import com.jme3.animation.Animation; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoader; import com.jme3.asset.BlenderKey; -import com.jme3.asset.BlenderKey.FeaturesToLoad; -import com.jme3.asset.BlenderKey.LoadingResults; import com.jme3.asset.ModelKey; import com.jme3.light.Light; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; import com.jme3.scene.CameraNode; import com.jme3.scene.LightNode; import com.jme3.scene.Node; @@ -55,16 +59,20 @@ import com.jme3.scene.plugins.blender.curves.CurvesHelper; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.landscape.LandscapeHelper; import com.jme3.scene.plugins.blender.lights.LightHelper; +import com.jme3.scene.plugins.blender.materials.MaterialContext; import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.meshes.MeshHelper; +import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.scene.plugins.blender.particles.ParticlesHelper; import com.jme3.scene.plugins.blender.textures.TextureHelper; +import com.jme3.texture.Texture; /** * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. @@ -83,72 +91,130 @@ public class BlenderLoader implements AssetLoader { try { this.setup(assetInfo); - List sceneBlocks = new ArrayList(); - BlenderKey blenderKey = blenderContext.getBlenderKey(); - LoadingResults loadingResults = blenderKey.prepareLoadingResults(); - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); animationHelper.loadAnimations(); - + + BlenderKey blenderKey = blenderContext.getBlenderKey(); + LoadedFeatures loadedFeatures = new LoadedFeatures(); for (FileBlockHeader block : blocks) { switch (block.getCode()) { - case FileBlockHeader.BLOCK_OB00:// Object + case BLOCK_OB00: ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext); - if (object instanceof LightNode) { - loadingResults.addLight((LightNode) object); - } else if (object instanceof CameraNode) { - loadingResults.addCamera((CameraNode) object); - } else if (object instanceof Node) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - } - if (this.isRootObject(loadingResults, (Node) object)) { - loadingResults.addObject((Node) object); - } + Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() }); } + if (object.getParent() == null) { + loadedFeatures.objects.add(object); + } + if (object instanceof LightNode && ((LightNode) object).getLight() != null) { + loadedFeatures.lights.add(((LightNode) object).getLight()); + } else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) { + loadedFeatures.cameras.add(((CameraNode) object).getCamera()); + } + break; + case BLOCK_SC00:// Scene + loadedFeatures.sceneBlocks.add(block); break; -// case FileBlockHeader.BLOCK_MA00:// Material -// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); -// MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext); -// if (blenderKey.isLoadUnlinkedAssets() && blenderKey.shouldLoad(FeaturesToLoad.MATERIALS)) { -// loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); -// } -// break; - case FileBlockHeader.BLOCK_SC00:// Scene - if (blenderKey.shouldLoad(FeaturesToLoad.SCENES)) { - sceneBlocks.add(block); + case BLOCK_MA00:// Material + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext); + loadedFeatures.materials.add(materialContext); + break; + case BLOCK_ME00:// Mesh + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext); + loadedFeatures.meshes.add(temporalMesh); + break; + case BLOCK_IM00:// Image + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext); + if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded + loadedFeatures.images.add(image); } break; - case FileBlockHeader.BLOCK_WO00:// World - if (blenderKey.shouldLoad(FeaturesToLoad.WORLD)) { - Structure worldStructure = block.getStructure(blenderContext); - String worldName = worldStructure.getName(); - if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { - LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); - Light ambientLight = landscapeHelper.toAmbientLight(worldStructure); - if(ambientLight != null) { - loadingResults.addLight(new LightNode(null, ambientLight)); - } - loadingResults.setSky(landscapeHelper.toSky(worldStructure)); - loadingResults.addFilter(landscapeHelper.toFog(worldStructure)); - loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure)); + case BLOCK_TE00: + Structure textureStructure = block.getStructure(blenderContext); + int type = ((Number) textureStructure.getFieldValue("type")).intValue(); + if (type == TextureHelper.TEX_IMAGE) { + TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class); + Texture texture = texHelper.getTexture(textureStructure, null, blenderContext); + if (texture != null) {// null is returned when texture has no image + loadedFeatures.textures.add(texture); } + } else { + LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object."); } break; + case BLOCK_WO00:// World + LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); + Structure worldStructure = block.getStructure(blenderContext); + + String worldName = worldStructure.getName(); + if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { + + Light ambientLight = landscapeHelper.toAmbientLight(worldStructure); + if (ambientLight != null) { + loadedFeatures.objects.add(new LightNode(null, ambientLight)); + loadedFeatures.lights.add(ambientLight); + } + loadedFeatures.sky = landscapeHelper.toSky(worldStructure); + loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure); + + Filter fogFilter = landscapeHelper.toFog(worldStructure); + if (fogFilter != null) { + loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure)); + } + } + break; + case BLOCK_AC00: + LOGGER.fine("Loading unlinked animations is not yet supported!"); + break; + default: + LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode()); } } - // bake constraints after everything is loaded + LOGGER.fine("Baking constraints after every feature is loaded."); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); constraintHelper.bakeConstraints(blenderContext); - // load the scene at the very end so that the root nodes have no parent during loading or constraints applying - for (FileBlockHeader sceneBlock : sceneBlocks) { - loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext))); + LOGGER.fine("Loading scenes and attaching them to the root object."); + for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) { + loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext))); + } + + LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it."); + Node modelRoot = new Node(blenderKey.getName()); + for (Node scene : loadedFeatures.scenes) { + modelRoot.attachChild(scene); } - return loadingResults; + if (blenderKey.isLoadUnlinkedAssets()) { + LOGGER.fine("Setting loaded content as user data in resulting sptaial."); + Map> linkedData = new HashMap>(); + + Map thisFileData = new HashMap(); + thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList() : loadedFeatures.scenes); + thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList() : loadedFeatures.objects); + thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList() : loadedFeatures.meshes); + thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList() : loadedFeatures.materials); + thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList() : loadedFeatures.textures); + thisFileData.put("images", loadedFeatures.images == null ? new ArrayList() : loadedFeatures.images); + thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList() : loadedFeatures.animations); + thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList() : loadedFeatures.cameras); + thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList() : loadedFeatures.lights); + thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList() : loadedFeatures.filters); + thisFileData.put("backgroundColor", loadedFeatures.backgroundColor); + thisFileData.put("sky", loadedFeatures.sky); + + linkedData.put("this", thisFileData); + linkedData.putAll(blenderContext.getLinkedFeatures()); + + modelRoot.setUserData("linkedData", linkedData); + } + + return modelRoot; } catch (BlenderFileException e) { throw new IOException(e.getLocalizedMessage(), e); } catch (Exception e) { @@ -158,62 +224,36 @@ public class BlenderLoader implements AssetLoader { } } - /** - * This method indicates if the given spatial is a root object. It means it - * has no parent or is directly attached to one of the already loaded scene - * nodes. - * - * @param loadingResults - * loading results containing the scene nodes - * @param spatial - * spatial object - * @return true if the given spatial is a root object and - * false otherwise - */ - protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) { - if (spatial.getParent() == null) { - return true; - } - for (Node scene : loadingResults.getScenes()) { - if (spatial.getParent().equals(scene)) { - return true; - } - } - return false; - } - /** * This method converts the given structure to a scene node. * @param structure * structure of a scene * @return scene's node + * @throws BlenderFileException + * an exception throw when problems with blender file occur */ - private Node toScene(Structure structure) { + private Node toScene(Structure structure) throws BlenderFileException { ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); Node result = new Node(structure.getName()); - try { - List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); - for (Structure b : base) { - Pointer pObject = (Pointer) b.getFieldValue("object"); - if (pObject.isNotNull()) { - Structure objectStructure = pObject.fetchData().get(0); + List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); + for (Structure b : base) { + Pointer pObject = (Pointer) b.getFieldValue("object"); + if (pObject.isNotNull()) { + Structure objectStructure = pObject.fetchData().get(0); - Object object = objectHelper.toObject(objectStructure, blenderContext); - if (object instanceof LightNode) { - result.addLight(((LightNode) object).getLight()); - result.attachChild((LightNode) object); - } else if (object instanceof Node) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - } - if (((Node) object).getParent() == null) { - result.attachChild((Spatial) object); - } + Object object = objectHelper.toObject(objectStructure, blenderContext); + if (object instanceof LightNode) { + result.addLight(((LightNode) object).getLight());// FIXME: check if this is needed !!! + result.attachChild((LightNode) object); + } else if (object instanceof Node) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + } + if (((Node) object).getParent() == null) { + result.attachChild((Spatial) object); } } } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); } return result; } @@ -261,7 +301,7 @@ public class BlenderLoader implements AssetLoader { blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext)); - + // reading the blocks (dna block is automatically saved in the blender context when found) FileBlockHeader sceneFileBlock = null; do { @@ -269,7 +309,7 @@ public class BlenderLoader implements AssetLoader { if (!fileBlock.isDnaBlock()) { blocks.add(fileBlock); // save the scene's file block - if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) { + if (fileBlock.getCode() == BlockCode.BLOCK_SC00) { sceneFileBlock = fileBlock; } } @@ -287,4 +327,39 @@ public class BlenderLoader implements AssetLoader { blenderContext = null; blocks = null; } + + /** + * This class holds the loading results according to the given loading flag. + * @author Marcin Roguski (Kaelthas) + */ + private static class LoadedFeatures { + private List sceneBlocks = new ArrayList(); + /** The scenes from the file. */ + private List scenes = new ArrayList(); + /** Objects from all scenes. */ + private List objects = new ArrayList(); + /** All meshes. */ + private List meshes = new ArrayList(); + /** Materials from all objects. */ + private List materials = new ArrayList(); + /** Textures from all objects. */ + private List textures = new ArrayList(); + /** The images stored in the blender file. */ + private List images = new ArrayList(); + /** Animations of all objects. */ + private List animations = new ArrayList(); + /** All cameras from the file. */ + private List cameras = new ArrayList(); + /** All lights from the file. */ + private List lights = new ArrayList(); + /** Loaded sky. */ + private Spatial sky; + /** Scene filters (ie. FOG). */ + private List filters = new ArrayList(); + /** + * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color + * is set to default (as in blender editor. + */ + private ColorRGBA backgroundColor = ColorRGBA.Gray; + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java index 021a6082f..eae047421 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java @@ -31,79 +31,10 @@ */ package com.jme3.scene.plugins.blender; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.asset.AssetInfo; -import com.jme3.asset.BlenderKey; -import com.jme3.asset.BlenderKey.FeaturesToLoad; -import com.jme3.scene.LightNode; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.animations.AnimationHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; - /** * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. - * + * @deprecated this class is deprecated; use BlenderLoader instead * @author Marcin Roguski (Kaelthas) */ public class BlenderModelLoader extends BlenderLoader { - - private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName()); - - @Override - public Spatial load(AssetInfo assetInfo) throws IOException { - try { - this.setup(assetInfo); - - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - animationHelper.loadAnimations(); - - BlenderKey blenderKey = blenderContext.getBlenderKey(); - List rootObjects = new ArrayList(); - for (FileBlockHeader block : blocks) { - if (block.getCode() == FileBlockHeader.BLOCK_OB00) { - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext); - if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - rootObjects.add((LightNode) object); - } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - if (((Node) object).getParent() == null) { - rootObjects.add((Node) object); - } - } - } - } - - // bake constraints after everything is loaded - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.bakeConstraints(blenderContext); - - // attach the nodes to the root node at the very end so that the root objects have no parents during constraint applying - LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene to it."); - Node modelRoot = new Node(blenderKey.getName()); - for (Node node : rootObjects) { - if (node instanceof LightNode) { - modelRoot.addLight(((LightNode) node).getLight()); - } - modelRoot.attachChild(node); - } - - return modelRoot; - } catch (BlenderFileException e) { - throw new IOException(e.getLocalizedMessage(), e); - } catch (Exception e) { - throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e); - } finally { - this.clear(); - } - } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java index 1b5a40e79..f8c1cdad3 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java @@ -25,6 +25,7 @@ import com.jme3.scene.plugins.blender.curves.BezierCurve; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.objects.ObjectHelper; @@ -48,7 +49,7 @@ public class AnimationHelper extends AbstractBlenderHelper { */ public void loadAnimations() throws BlenderFileException { LOGGER.info("Loading animations that will be later applied to scene features."); - List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); + List actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00); if (actionHeaders != null) { for (FileBlockHeader header : actionHeaders) { Structure actionStructure = header.getStructure(blenderContext); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java index c5e1f0192..70cb09b1f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java @@ -5,7 +5,6 @@ import java.util.logging.Logger; import com.jme3.math.FastMath; import com.jme3.renderer.Camera; -import com.jme3.scene.CameraNode; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; @@ -43,7 +42,7 @@ public class CameraHelper extends AbstractBlenderHelper { * an exception is thrown when there are problems with the * blender file */ - public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { if (blenderVersion >= 250) { return this.toCamera250(structure, blenderContext.getSceneStructure()); } else { @@ -63,7 +62,7 @@ public class CameraHelper extends AbstractBlenderHelper { * an exception is thrown when there are problems with the * blender file */ - private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { + private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { int width = DEFAULT_CAM_WIDTH; int height = DEFAULT_CAM_HEIGHT; if (sceneStructure != null) { @@ -99,7 +98,7 @@ public class CameraHelper extends AbstractBlenderHelper { sensor = ((Number) structure.getFieldValue(sensorName)).floatValue(); } float focalLength = ((Number) structure.getFieldValue("lens")).floatValue(); - float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength); + float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength); if (sensorVertical) { fovY = fov * FastMath.RAD_TO_DEG; } else { @@ -111,7 +110,8 @@ public class CameraHelper extends AbstractBlenderHelper { fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); } camera.setFrustumPerspective(fovY, aspect, clipsta, clipend); - return new CameraNode(null, camera); + camera.setName(structure.getName()); + return camera; } /** @@ -124,7 +124,7 @@ public class CameraHelper extends AbstractBlenderHelper { * an exception is thrown when there are problems with the * blender file */ - private CameraNode toCamera249(Structure structure) throws BlenderFileException { + private Camera toCamera249(Structure structure) throws BlenderFileException { Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); int type = ((Number) structure.getFieldValue("type")).intValue(); if (type != 0 && type != 1) { @@ -142,6 +142,7 @@ public class CameraHelper extends AbstractBlenderHelper { aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); } camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend); - return new CameraNode(null, camera); + camera.setName(structure.getName()); + return camera; } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java index 7bd634254..666da7896 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java @@ -31,6 +31,8 @@ */ package com.jme3.scene.plugins.blender.file; +import java.util.logging.Logger; + import com.jme3.scene.plugins.blender.BlenderContext; /** @@ -39,39 +41,23 @@ import com.jme3.scene.plugins.blender.BlenderContext; * @author Marcin Roguski */ public class FileBlockHeader { + private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName()); - public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; // TE00 - public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; // ME00 - public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; // SR00 - public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; // CA00 - public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; // LA00 - public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; // OB00 - public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; // MA00 - public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; // SC00 - public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; // WO00 - public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; // TX00 - public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; // IP00 - public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; // AC00 - public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB - public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND - public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA - public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1 - public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB /** Identifier of the file-block [4 bytes]. */ - private int code; + private BlockCode code; /** Total length of the data after the file-block-header [4 bytes]. */ - private int size; + private int size; /** * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer * size)]. */ - private long oldMemoryAddress; + private long oldMemoryAddress; /** Index of the SDNA structure [4 bytes]. */ - private int sdnaIndex; + private int sdnaIndex; /** Number of structure located in this file-block [4 bytes]. */ - private int count; + private int count; /** Start position of the block's data in the stream. */ - private int blockPosition; + private int blockPosition; /** * Constructor. Loads the block header from the given stream during instance creation. @@ -84,13 +70,13 @@ public class FileBlockHeader { */ public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { inputStream.alignPosition(4); - code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte()); size = inputStream.readInt(); oldMemoryAddress = inputStream.readPointer(); sdnaIndex = inputStream.readInt(); count = inputStream.readInt(); blockPosition = inputStream.getPosition(); - if (FileBlockHeader.BLOCK_DNA1 == code) { + if (BlockCode.BLOCK_DNA1 == code) { blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext)); } else { inputStream.setPosition(blockPosition + size); @@ -116,7 +102,7 @@ public class FileBlockHeader { * This method returns the code of this data block. * @return the code of this data block */ - public int getCode() { + public BlockCode getCode() { return code; } @@ -157,7 +143,7 @@ public class FileBlockHeader { * @return true if this block is the last one in the file nad false otherwise */ public boolean isLastBlock() { - return FileBlockHeader.BLOCK_ENDB == code; + return BlockCode.BLOCK_ENDB == code; } /** @@ -165,25 +151,44 @@ public class FileBlockHeader { * @return true if this block is the SDNA block and false otherwise */ public boolean isDnaBlock() { - return FileBlockHeader.BLOCK_DNA1 == code; + return BlockCode.BLOCK_DNA1 == code; } @Override public String toString() { - return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]"; + return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]"; } - /** - * This method transforms the coded bloch id into a string value. - * @param code - * the id of the block - * @return the string value of the block id - */ - protected String codeToString(int code) { - char c1 = (char) ((code & 0xFF000000) >> 24); - char c2 = (char) ((code & 0xFF0000) >> 16); - char c3 = (char) ((code & 0xFF00) >> 8); - char c4 = (char) (code & 0xFF); - return String.valueOf(c1) + c2 + c3 + c4; + public static enum BlockCode { + BLOCK_ME00('M' << 24 | 'E' << 16), // mesh + BLOCK_CA00('C' << 24 | 'A' << 16), // camera + BLOCK_LA00('L' << 24 | 'A' << 16), // lamp + BLOCK_OB00('O' << 24 | 'B' << 16), // object + BLOCK_MA00('M' << 24 | 'A' << 16), // material + BLOCK_SC00('S' << 24 | 'C' << 16), // scene + BLOCK_WO00('W' << 24 | 'O' << 16), // world + BLOCK_TX00('T' << 24 | 'X' << 16), // texture + BLOCK_IP00('I' << 24 | 'P' << 16), // ipo + BLOCK_AC00('A' << 24 | 'C' << 16), // action + BLOCK_IM00('I' << 24 | 'M' << 16), // image + BLOCK_TE00('T' << 24 | 'E' << 16), BLOCK_WM00('W' << 24 | 'M' << 16), BLOCK_SR00('S' << 24 | 'R' << 16), BLOCK_SN00('S' << 24 | 'N' << 16), BLOCK_BR00('B' << 24 | 'R' << 16), BLOCK_LS00('L' << 24 | 'S' << 16), BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), BLOCK_TEST('T' << 24 | 'E' << 16 + | 'S' << 8 | 'T'), BLOCK_UNKN(0); + + private int code; + + private BlockCode(int code) { + this.code = code; + } + + public static BlockCode valueOf(int code) { + for (BlockCode blockCode : BlockCode.values()) { + if (blockCode.code == code) { + return blockCode; + } + } + byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) }; + LOGGER.warning("Unknown block header: " + new String(codeBytes)); + return BLOCK_UNKN; + } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java index 4f0de7e04..941c7a8fc 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java @@ -254,7 +254,8 @@ public class Structure implements Cloneable { Structure id = (Structure) fieldValue; return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix } - return null; + Object name = this.getFieldValue("name", null); + return name == null ? null : name.toString().substring(2); } @Override diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java index 17e38c92b..3ae866b7c 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java @@ -40,7 +40,6 @@ import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; -import com.jme3.scene.LightNode; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; @@ -67,8 +66,8 @@ public class LightHelper extends AbstractBlenderHelper { super(blenderVersion, blenderContext); } - public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); + public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } @@ -111,6 +110,7 @@ public class LightHelper extends AbstractBlenderHelper { float g = ((Number) structure.getFieldValue("g")).floatValue(); float b = ((Number) structure.getFieldValue("b")).floatValue(); light.setColor(new ColorRGBA(r, g, b, 1.0f)); - return new LightNode(structure.getName(), light); + light.setName(structure.getName()); + return light; } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java index 6e67da967..51c7072b1 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -1,11 +1,15 @@ package com.jme3.scene.plugins.blender.materials; +import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.material.RenderState.BlendMode; import com.jme3.material.RenderState.FaceCullMode; @@ -30,7 +34,7 @@ import com.jme3.util.BufferUtils; * This class holds the data about the material. * @author Marcin Roguski (Kaelthas) */ -public final class MaterialContext { +public final class MaterialContext implements Savable { private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); // texture mapping types @@ -67,7 +71,7 @@ public final class MaterialContext { int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); diffuseShader = DiffuseShader.values()[diff_shader]; ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue(); - + if (shadeless) { float r = ((Number) structure.getFieldValue("r")).floatValue(); float g = ((Number) structure.getFieldValue("g")).floatValue(); @@ -107,6 +111,13 @@ public final class MaterialContext { this.transparent = transparent; } + /** + * @return the name of the material + */ + public String getName() { + return name; + } + /** * Applies material to a given geometry. * @@ -314,4 +325,14 @@ public final class MaterialContext { float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); return new ColorRGBA(r, g, b, alpha); } + + @Override + public void write(JmeExporter e) throws IOException { + throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!"); + } + + @Override + public void read(JmeImporter e) throws IOException { + throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!"); + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java index 5fcccc389..0809cec9a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java @@ -161,12 +161,17 @@ public class MaterialHelper extends AbstractBlenderHelper { * an exception is throw when problems with blend file occur */ public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading material."); MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } + if ("ID".equals(structure.getType())) { + LOGGER.fine("Loading material from external blend file."); + return (MaterialContext) this.loadLibrary(structure); + } + + LOGGER.fine("Loading material."); result = new MaterialContext(structure, blenderContext); LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); Long oma = structure.getOldMemoryAddress(); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index 92b236f4c..5284b964a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -40,7 +40,6 @@ import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector2f; @@ -52,7 +51,6 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.objects.Properties; @@ -106,17 +104,18 @@ public class MeshHelper extends AbstractBlenderHelper { return temporalMesh.clone(); } + if ("ID".equals(meshStructure.getType())) { + LOGGER.fine("Loading mesh from external blend file."); + return (TemporalMesh) this.loadLibrary(meshStructure); + } + String name = meshStructure.getName(); LOGGER.log(Level.FINE, "Reading mesh: {0}.", name); temporalMesh = new TemporalMesh(meshStructure, blenderContext); LOGGER.fine("Loading materials."); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - MaterialContext[] materials = null; - if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { - materials = materialHelper.getMaterials(meshStructure, blenderContext); - } - temporalMesh.setMaterials(materials); + temporalMesh.setMaterials(materialHelper.getMaterials(meshStructure, blenderContext)); LOGGER.fine("Reading custom properties."); Properties properties = this.loadProperties(meshStructure, blenderContext); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index 8e2dc2652..17c0d421e 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -40,12 +40,15 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.light.Light; import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; import com.jme3.math.Transform; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.CameraNode; import com.jme3.scene.Geometry; +import com.jme3.scene.LightNode; import com.jme3.scene.Mesh.Mode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; @@ -106,39 +109,34 @@ public class ObjectHelper extends AbstractBlenderHelper { * an exception is thrown when the given data is inapropriate */ public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.fine("Loading blender object."); + Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); + if (loadedResult != null) { + return loadedResult; + } + LOGGER.fine("Loading blender object."); + if ("ID".equals(objectStructure.getType())) { + Node object = (Node) this.loadLibrary(objectStructure); + if (object.getParent() != null) { + LOGGER.log(Level.FINEST, "Detaching object {0}, loaded from external file, from its parent.", object); + object.getParent().detachChild(object); + } + return object; + } int type = ((Number) objectStructure.getFieldValue("type")).intValue(); ObjectType objectType = ObjectType.valueOf(type); LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType); - if (objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) { - LOGGER.fine("Lamps are not included in loading."); - return null; - } - if (objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) { - LOGGER.fine("Cameras are not included in loading."); - return null; - } - if (!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) { - LOGGER.fine("Objects are not included in loading."); - return null; - } + int lay = ((Number) objectStructure.getFieldValue("lay")).intValue(); if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) { LOGGER.fine("The layer this object is located in is not included in loading."); return null; } - LOGGER.fine("Checking if the object has not been already loaded."); - Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (loadedResult != null) { - return loadedResult; - } - blenderContext.pushParent(objectStructure); String name = objectStructure.getName(); LOGGER.log(Level.FINE, "Loading obejct: {0}", name); - + int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue(); boolean visible = (restrictflag & 0x01) != 0; @@ -171,7 +169,7 @@ public class ObjectHelper extends AbstractBlenderHelper { Pointer pMesh = (Pointer) objectStructure.getFieldValue("data"); List meshesArray = pMesh.fetchData(); TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext); - if(temporalMesh != null) { + if (temporalMesh != null) { result.attachChild(temporalMesh); } break; @@ -183,7 +181,7 @@ public class ObjectHelper extends AbstractBlenderHelper { CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); Structure curveData = pCurve.fetchData().get(0); TemporalMesh curvesTemporalMesh = curvesHelper.toCurve(curveData, blenderContext); - if(curvesTemporalMesh != null) { + if (curvesTemporalMesh != null) { result.attachChild(curvesTemporalMesh); } } @@ -193,10 +191,12 @@ public class ObjectHelper extends AbstractBlenderHelper { if (pLamp.isNotNull()) { LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); List lampsArray = pLamp.fetchData(); - result = lightHelper.toLight(lampsArray.get(0), blenderContext); - if (result == null) { + Light light = lightHelper.toLight(lampsArray.get(0), blenderContext); + if (light == null) { // probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes result = new Node(name); + } else { + result = new LightNode(name, light); } } break; @@ -205,19 +205,25 @@ public class ObjectHelper extends AbstractBlenderHelper { if (pCamera.isNotNull()) { CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); List camerasArray = pCamera.fetchData(); - result = cameraHelper.toCamera(camerasArray.get(0), blenderContext); + Camera camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext); + if (camera == null) { + // just create a node so that we can maintain child-parent relationship for nodes + result = new Node(name); + } else { + result = new CameraNode(name, camera); + } } break; default: LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type); } - + if (result != null) { LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released)."); Long oma = objectStructure.getOldMemoryAddress(); blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure); blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result); - + blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress()); if (objectType == ObjectType.ARMATURE) { blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE); @@ -235,13 +241,13 @@ public class ObjectHelper extends AbstractBlenderHelper { for (Modifier modifier : modifiers) { modifier.apply(result, blenderContext); } - + if (result.getChildren() != null && result.getChildren().size() > 0) { - if(result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) { + if (result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) { LOGGER.fine("Converting temporal mesh into jme geometries."); - ((TemporalMesh)result.getChild(0)).toGeometries(); + ((TemporalMesh) result.getChild(0)).toGeometries(); } - + LOGGER.fine("Applying proper scale to the geometries."); for (Spatial child : result.getChildren()) { if (child instanceof Geometry) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java index ad35a2ebc..f6ed89ce7 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -121,7 +121,7 @@ public class TextureHelper extends AbstractBlenderHelper { * data. The returned texture has the name set to the value of its blender * type. * - * @param tex + * @param textureStructure * texture structure filled with data * @param blenderContext * the blender context @@ -130,23 +130,29 @@ public class TextureHelper extends AbstractBlenderHelper { * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ - public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { - Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedDataType.FEATURE); + public Texture getTexture(Structure textureStructure, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { + Texture result = (Texture) blenderContext.getLoadedFeature(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } - int type = ((Number) tex.getFieldValue("type")).intValue(); - int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue(); + + if ("ID".equals(textureStructure.getType())) { + LOGGER.fine("Loading texture from external blend file."); + return (Texture) this.loadLibrary(textureStructure); + } + + int type = ((Number) textureStructure.getFieldValue("type")).intValue(); + int imaflag = ((Number) textureStructure.getFieldValue("imaflag")).intValue(); switch (type) { case TEX_IMAGE:// (it is first because probably this will be most commonly used) - Pointer pImage = (Pointer) tex.getFieldValue("ima"); + Pointer pImage = (Pointer) textureStructure.getFieldValue("ima"); if (pImage.isNotNull()) { Structure image = pImage.fetchData().get(0); - Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext); + Texture loadedTexture = this.loadImageAsTexture(image, imaflag, blenderContext); if (loadedTexture != null) { result = loadedTexture; - this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext); + this.applyColorbandAndColorFactors(textureStructure, result.getImage(), blenderContext); } } break; @@ -160,7 +166,7 @@ public class TextureHelper extends AbstractBlenderHelper { case TEX_MUSGRAVE: case TEX_VORONOI: case TEX_DISTNOISE: - result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); + result = new GeneratedTexture(textureStructure, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); break; case TEX_NONE:// No texture, do nothing break; @@ -169,13 +175,13 @@ public class TextureHelper extends AbstractBlenderHelper { case TEX_PLUGIN: case TEX_ENVMAP: case TEX_OCEAN: - LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() }); + LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, textureStructure.getName() }); break; default: - throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName()); + throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + textureStructure.getName()); } if (result != null) { - result.setName(tex.getName()); + result.setName(textureStructure.getName()); result.setWrap(WrapMode.Repeat); // decide if the mipmaps will be generated @@ -195,14 +201,14 @@ public class TextureHelper extends AbstractBlenderHelper { } if (type != TEX_IMAGE) {// only generated textures should have this key - result.setKey(new GeneratedTextureKey(tex.getName())); + result.setKey(new GeneratedTextureKey(textureStructure.getName())); } if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() }); + LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), textureStructure.getOldMemoryAddress() }); } - blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.STRUCTURE, tex); - blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.FEATURE, result); + blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, textureStructure); + blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result); } return result; } @@ -222,30 +228,40 @@ public class TextureHelper extends AbstractBlenderHelper { * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ - protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { + public Texture loadImageAsTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); Texture result = null; Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (im == null) { - String texturePath = imageStructure.getFieldValue("name").toString(); - Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); - if (pPackedFile.isNull()) { - LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); - result = this.loadImageFromFile(texturePath, imaflag, blenderContext); + if ("ID".equals(imageStructure.getType())) { + LOGGER.fine("Loading texture from external blend file."); + result = (Texture) this.loadLibrary(imageStructure); } else { - LOGGER.fine("Packed texture. Reading directly from the blend file!"); - Structure packedFile = pPackedFile.fetchData().get(0); - Pointer pData = (Pointer) packedFile.getFieldValue("data"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); - blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); - ImageLoader imageLoader = new ImageLoader(); - - // Should the texture be flipped? It works for sinbad .. - result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); + String texturePath = imageStructure.getFieldValue("name").toString(); + Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); + if (pPackedFile.isNull()) { + LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); + result = this.loadImageFromFile(texturePath, imaflag, blenderContext); + } else { + LOGGER.fine("Packed texture. Reading directly from the blend file!"); + Structure packedFile = pPackedFile.fetchData().get(0); + Pointer pData = (Pointer) packedFile.getFieldValue("data"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); + blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); + + // Should the texture be flipped? It works for sinbad .. + result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); + } } } else { result = new Texture2D(im); } + + if (result != null) {// render result is not being loaded + blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure); + blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result.getImage()); + result.setName(imageStructure.getName()); + } return result; } @@ -524,6 +540,18 @@ public class TextureHelper extends AbstractBlenderHelper { return result; } + /** + * Reads the texture data from the given material or sky structure. + * @param structure + * the structure of material or sky + * @param diffuseColorArray + * array of diffuse colors + * @param skyTexture + * indicates it we're going to read sky texture or not + * @return a list of combined textures + * @throws BlenderFileException + * an exception is thrown when problems with reading the blend file occur + */ @SuppressWarnings("unchecked") public List readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException { DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java index 5c3504771..8ff55baac 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java @@ -1,10 +1,12 @@ package com.jme3.scene.plugins.blender.textures.blending; +import java.util.logging.Logger; + +import jme3tools.converters.MipMapGenerator; + import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.texture.Image; -import java.util.logging.Logger; -import jme3tools.converters.MipMapGenerator; /** * An abstract class that contains the basic methods used by the classes that @@ -103,12 +105,12 @@ import jme3tools.converters.MipMapGenerator; public void copyBlendingData(TextureBlender textureBlender) { if (textureBlender instanceof AbstractTextureBlender) { - this.flag = ((AbstractTextureBlender) textureBlender).flag; - this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; - this.blendType = ((AbstractTextureBlender) textureBlender).blendType; - this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); - this.color = ((AbstractTextureBlender) textureBlender).color.clone(); - this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; + flag = ((AbstractTextureBlender) textureBlender).flag; + negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; + blendType = ((AbstractTextureBlender) textureBlender).blendType; + materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); + color = ((AbstractTextureBlender) textureBlender).color.clone(); + blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; } else { LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java index c682ed1e5..f487e240d 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java @@ -31,13 +31,13 @@ */ package com.jme3.scene.plugins.blender.textures.blending; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; -import java.util.logging.Level; -import java.util.logging.Logger; - /** * This class creates the texture blending class depending on the texture type. * @@ -66,7 +66,6 @@ public class TextureBlenderFactory { * the texture format * @return texture blending class */ - @SuppressWarnings("deprecation") public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { switch (format) { case Luminance8: diff --git a/jme3-core/src/main/java/com/jme3/scene/UserData.java b/jme3-core/src/main/java/com/jme3/scene/UserData.java index 4b5d70404..3047618fc 100644 --- a/jme3-core/src/main/java/com/jme3/scene/UserData.java +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -33,6 +33,12 @@ package com.jme3.scene; import com.jme3.export.*; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * UserData is used to contain user data objects @@ -48,28 +54,40 @@ public final class UserData implements Savable { * shape generation should ignore them. */ public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore"; - + /** * For geometries using shared mesh, this will specify the shared * mesh reference. */ - public static final String JME_SHAREDMESH = "JmeSharedMesh"; - - protected byte type; - protected Object value; + public static final String JME_SHAREDMESH = "JmeSharedMesh"; + + private static final int TYPE_INTEGER = 0; + private static final int TYPE_FLOAT = 1; + private static final int TYPE_BOOLEAN = 2; + private static final int TYPE_STRING = 3; + private static final int TYPE_LONG = 4; + private static final int TYPE_SAVABLE = 5; + private static final int TYPE_LIST = 6; + private static final int TYPE_MAP = 7; + private static final int TYPE_ARRAY = 8; + + protected byte type; + protected Object value; public UserData() { } /** - * Creates a new UserData with the given + * Creates a new UserData with the given * type and value. * - * @param type Type of data, should be between 0 and 4. - * @param value Value of the data + * @param type + * Type of data, should be between 0 and 8. + * @param value + * Value of the data */ public UserData(byte type, Object value) { - assert type >= 0 && type <= 4; + assert type >= 0 && type <= 8; this.type = type; this.value = value; } @@ -85,15 +103,23 @@ public final class UserData implements Savable { public static byte getObjectType(Object type) { if (type instanceof Integer) { - return 0; + return TYPE_INTEGER; } else if (type instanceof Float) { - return 1; + return TYPE_FLOAT; } else if (type instanceof Boolean) { - return 2; + return TYPE_BOOLEAN; } else if (type instanceof String) { - return 3; + return TYPE_STRING; } else if (type instanceof Long) { - return 4; + return TYPE_LONG; + } else if (type instanceof Savable) { + return TYPE_SAVABLE; + } else if (type instanceof List) { + return TYPE_LIST; + } else if (type instanceof Map) { + return TYPE_MAP; + } else if (type instanceof Object[]) { + return TYPE_ARRAY; } else { throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName()); } @@ -101,56 +127,195 @@ public final class UserData implements Savable { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); - oc.write(type, "type", (byte)0); + oc.write(type, "type", (byte) 0); switch (type) { - case 0: + case TYPE_INTEGER: int i = (Integer) value; oc.write(i, "intVal", 0); break; - case 1: + case TYPE_FLOAT: float f = (Float) value; oc.write(f, "floatVal", 0f); break; - case 2: + case TYPE_BOOLEAN: boolean b = (Boolean) value; oc.write(b, "boolVal", false); break; - case 3: + case TYPE_STRING: String s = (String) value; oc.write(s, "strVal", null); break; - case 4: + case TYPE_LONG: Long l = (Long) value; oc.write(l, "longVal", 0l); break; + case TYPE_SAVABLE: + Savable sav = (Savable) value; + oc.write(sav, "savableVal", null); + break; + case TYPE_LIST: + this.writeList(oc, (List) value, "0"); + break; + case TYPE_MAP: + Map map = (Map) value; + this.writeList(oc, map.keySet(), "0"); + this.writeList(oc, map.values(), "1"); + break; + case TYPE_ARRAY: + this.writeList(oc, Arrays.asList((Object[]) value), "0"); + break; default: - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unsupported value type: " + value.getClass()); } } public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readByte("type", (byte) 0); - switch (type) { - case 0: + case TYPE_INTEGER: value = ic.readInt("intVal", 0); break; - case 1: + case TYPE_FLOAT: value = ic.readFloat("floatVal", 0f); break; - case 2: + case TYPE_BOOLEAN: value = ic.readBoolean("boolVal", false); break; - case 3: + case TYPE_STRING: value = ic.readString("strVal", null); break; - case 4: + case TYPE_LONG: value = ic.readLong("longVal", 0l); break; + case TYPE_SAVABLE: + value = ic.readSavable("savableVal", null); + break; + case TYPE_LIST: + value = this.readList(ic, "0"); + break; + case TYPE_MAP: + Map map = new HashMap(); + List keys = this.readList(ic, "0"); + List values = this.readList(ic, "1"); + for (int i = 0; i < keys.size(); ++i) { + map.put(keys.get(i), values.get(i)); + } + value = map; + break; + case TYPE_ARRAY: + value = this.readList(ic, "0").toArray(); + break; default: - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unknown type of stored data: " + type); + } + } + + /** + * The method stores a list in the capsule. + * @param oc + * output capsule + * @param list + * the list to be stored + * @throws IOException + */ + private void writeList(OutputCapsule oc, Collection list, String listName) throws IOException { + if (list != null) { + oc.write(list.size(), listName + "size", 0); + int counter = 0; + for (Object o : list) { + // t is for 'type'; v is for 'value' + if (o instanceof Integer) { + oc.write(TYPE_INTEGER, listName + "t" + counter, 0); + oc.write((Integer) o, listName + "v" + counter, 0); + } else if (o instanceof Float) { + oc.write(TYPE_FLOAT, listName + "t" + counter, 0); + oc.write((Float) o, listName + "v" + counter, 0f); + } else if (o instanceof Boolean) { + oc.write(TYPE_BOOLEAN, listName + "t" + counter, 0); + oc.write((Boolean) o, listName + "v" + counter, false); + } else if (o instanceof String || o == null) {// treat null's like Strings just to store them and keep the List like the user intended + oc.write(TYPE_STRING, listName + "t" + counter, 0); + oc.write((String) o, listName + "v" + counter, null); + } else if (o instanceof Long) { + oc.write(TYPE_LONG, listName + "t" + counter, 0); + oc.write((Long) o, listName + "v" + counter, 0L); + } else if (o instanceof Savable) { + oc.write(TYPE_SAVABLE, listName + "t" + counter, 0); + oc.write((Savable) o, listName + "v" + counter, null); + } else if(o instanceof Object[]) { + oc.write(TYPE_ARRAY, listName + "t" + counter, 0); + this.writeList(oc, Arrays.asList((Object[]) o), listName + "v" + counter); + } else if(o instanceof List) { + oc.write(TYPE_LIST, listName + "t" + counter, 0); + this.writeList(oc, (List) o, listName + "v" + counter); + } else if(o instanceof Map) { + oc.write(TYPE_MAP, listName + "t" + counter, 0); + Map map = (Map) o; + this.writeList(oc, map.keySet(), listName + "v(keys)" + counter); + this.writeList(oc, map.values(), listName + "v(vals)" + counter); + } else { + throw new UnsupportedOperationException("Unsupported type stored in the list: " + o.getClass()); + } + + ++counter; + } + } else { + oc.write(0, "size", 0); + } + } + + /** + * The method loads a list from the given input capsule. + * @param ic + * the input capsule + * @return loaded list (an empty list in case its size is 0) + * @throws IOException + */ + private List readList(InputCapsule ic, String listName) throws IOException { + int size = ic.readInt(listName + "size", 0); + List list = new ArrayList(size); + for (int i = 0; i < size; ++i) { + int type = ic.readInt(listName + "t" + i, 0); + switch (type) { + case TYPE_INTEGER: + list.add(ic.readInt(listName + "v" + i, 0)); + break; + case TYPE_FLOAT: + list.add(ic.readFloat(listName + "v" + i, 0)); + break; + case TYPE_BOOLEAN: + list.add(ic.readBoolean(listName + "v" + i, false)); + break; + case TYPE_STRING: + list.add(ic.readString(listName + "v" + i, null)); + break; + case TYPE_LONG: + list.add(ic.readLong(listName + "v" + i, 0L)); + break; + case TYPE_SAVABLE: + list.add(ic.readSavable(listName + "v" + i, null)); + break; + case TYPE_ARRAY: + list.add(this.readList(ic, listName + "v" + i).toArray()); + break; + case TYPE_LIST: + list.add(this.readList(ic, listName + "v" + i)); + break; + case TYPE_MAP: + Map map = new HashMap(); + List keys = this.readList(ic, listName + "v(keys)" + i); + List values = this.readList(ic, listName + "v(vals)" + i); + for (int j = 0; j < keys.size(); ++j) { + map.put(keys.get(j), values.get(j)); + } + list.add(map); + break; + default: + throw new UnsupportedOperationException("Unknown type of stored data in a list: " + type); + } } + return list; } } From a5c98a59be27285100cb41c0bca705753329c06a Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Mon, 23 Mar 2015 21:01:21 +0100 Subject: [PATCH 015/225] Refactoring: catching up wth latest jme3 core changes. --- .../blender/landscape/LandscapeHelper.java | 2 +- .../blender/materials/MaterialHelper.java | 3 ++- .../blender/textures/CombinedTexture.java | 8 +++++- .../plugins/blender/textures/ImageUtils.java | 11 ++++---- .../blender/textures/TriangulatedTexture.java | 27 ++++++++++--------- .../textures/blending/TextureBlenderAWT.java | 4 ++- .../textures/blending/TextureBlenderDDS.java | 5 +++- .../blending/TextureBlenderLuminance.java | 4 ++- .../textures/io/AWTPixelInputOutput.java | 18 +++++++++++++ .../blender/textures/io/PixelIOFactory.java | 2 ++ 10 files changed, 60 insertions(+), 24 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java index c05eed775..8466d5cce 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java @@ -208,6 +208,6 @@ public class LandscapeHelper extends AbstractBlenderHelper { } LOGGER.fine("Sky texture created. Creating sky."); - return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false); + return SkyFactory.createSky(blenderContext.getAssetManager(), texture, SkyFactory.EnvMapType.CubeMap); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java index 0809cec9a..885c5850e 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java @@ -51,6 +51,7 @@ import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.shader.VarType; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ColorSpace; import com.jme3.texture.Texture; import com.jme3.util.BufferUtils; @@ -217,7 +218,7 @@ public class MaterialHelper extends AbstractBlenderHelper { } } - image = new Image(Format.RGBA8, w, h, bb); + image = new Image(Format.RGBA8, w, h, bb, ColorSpace.Linear); texture.setImage(image); result.setTextureParam("Texture", VarType.Texture2D, texture); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java index 5bfc9eb78..b7d6958ca 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -444,13 +444,17 @@ public class CombinedTexture { case RGB8: return true;// these types have no alpha by definition case ABGR8: + case DXT1A: case DXT3: case DXT5: case Luminance16FAlpha16F: case Luminance8Alpha8: case RGBA16F: case RGBA32F: - case RGBA8:// with these types it is better to make sure if the texture is or is not transparent + case RGBA8: + case ARGB8: + case BGRA8: + case RGB5A1:// with these types it is better to make sure if the texture is or is not transparent PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat()); TexturePixel pixel = new TexturePixel(); int depth = image.getDepth() == 0 ? 1 : image.getDepth(); @@ -465,6 +469,8 @@ public class CombinedTexture { } } return true; + default: + throw new IllegalStateException("Unknown image format: " + image.getFormat()); } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java index 66740769d..99ea9b5d2 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java @@ -41,13 +41,13 @@ public final class ImageUtils { public static Image createEmptyImage(Format format, int width, int height, int depth) { int bufferSize = width * height * (format.getBitsPerPixel() >> 3); if (depth < 2) { - return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize)); + return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize), com.jme3.texture.image.ColorSpace.Linear); } ArrayList data = new ArrayList(depth); for (int i = 0; i < depth; ++i) { data.add(BufferUtils.createByteBuffer(bufferSize)); } - return new Image(Format.RGB8, width, height, depth, data); + return new Image(Format.RGB8, width, height, depth, data, com.jme3.texture.image.ColorSpace.Linear); } /** @@ -337,7 +337,7 @@ public final class ImageUtils { alphas[0] = data.get() * 255.0f; alphas[1] = data.get() * 255.0f; //the casts to long must be done here because otherwise 32-bit integers would be shifetd by 32 and 40 bits which would result in improper values - long alphaIndices = (long)data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40; + long alphaIndices = data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40; if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. alphas[2] = (6 * alphas[0] + alphas[1]) / 7; alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; @@ -404,7 +404,8 @@ public final class ImageUtils { dataArray.add(BufferUtils.createByteBuffer(bytes)); } - Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0)); + Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray, com.jme3.texture.image.ColorSpace.Linear) : + new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0), com.jme3.texture.image.ColorSpace.Linear); if (newMipmapSizes != null) { result.setMipMapSizes(newMipmapSizes); } @@ -467,6 +468,6 @@ public final class ImageUtils { private static Image toJmeImage(BufferedImage bufferedImage, Format format) { ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3); ImageToAwt.convert(bufferedImage, format, byteBuffer); - return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer); + return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer, com.jme3.texture.image.ColorSpace.Linear); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java index fd4044244..9a4889109 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java @@ -31,6 +31,7 @@ import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; /** @@ -77,7 +78,7 @@ import com.jme3.util.BufferUtils; for (int i = 0; i < facesCount; ++i) { faceTextures.add(new TriangleTextureElement(i, texture2d.getImage(), uvs, true, blenderContext)); } - this.format = texture2d.getImage().getFormat(); + format = texture2d.getImage().getFormat(); } /** @@ -113,7 +114,7 @@ import com.jme3.util.BufferUtils; */ public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) { Format newFormat = null; - for (TriangleTextureElement triangleTextureElement : this.faceTextures) { + for (TriangleTextureElement triangleTextureElement : faceTextures) { Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext); if (newFormat == null) { @@ -122,7 +123,7 @@ import com.jme3.util.BufferUtils; throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); } } - this.format = newFormat; + format = newFormat; } /** @@ -242,7 +243,7 @@ import com.jme3.util.BufferUtils; resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]); } - Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3))); + Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3)), ColorSpace.Linear); resultTexture = new Texture2D(resultImage); for (Entry entry : imageLayoutData.entrySet()) { if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) { @@ -420,7 +421,7 @@ import com.jme3.util.BufferUtils; data.put(pixel.getA8()); } } - image = new Image(Format.RGBA8, width, height, data); + image = new Image(Format.RGBA8, width, height, data, ColorSpace.Linear); // modify the UV values so that they fit the new image float heightUV = maxUVY - minUVY; @@ -481,7 +482,7 @@ import com.jme3.util.BufferUtils; imageHeight = 1; } ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3)); - image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data); + image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data, ColorSpace.Linear); // computing the pixels PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat); @@ -529,8 +530,8 @@ import com.jme3.util.BufferUtils; public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) { for (int i = 0; i < 3; ++i) { result[i] = new Vector2f(); - result[i].x = xPos / (float) totalImageWidth + this.uv[i].x * (this.image.getWidth() / (float) totalImageWidth); - result[i].y = yPos / (float) totalImageHeight + this.uv[i].y * (this.image.getHeight() / (float) totalImageHeight); + result[i].x = xPos / (float) totalImageWidth + uv[i].x * (image.getWidth() / (float) totalImageWidth); + result[i].y = yPos / (float) totalImageHeight + uv[i].y * (image.getHeight() / (float) totalImageHeight); } } @@ -623,9 +624,9 @@ import com.jme3.util.BufferUtils; * a position in 3D space */ public RectangleEnvelope(Vector3f pointPosition) { - this.min = pointPosition; - this.h = this.w = Vector3f.ZERO; - this.width = this.height = 1; + min = pointPosition; + h = w = Vector3f.ZERO; + width = height = 1; } /** @@ -642,8 +643,8 @@ import com.jme3.util.BufferUtils; this.min = min; this.h = h; this.w = w; - this.width = w.length(); - this.height = h.length(); + width = w.length(); + height = h.length(); } @Override diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java index d7f26b1be..1d7bf2146 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java @@ -38,7 +38,9 @@ import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; + import java.nio.ByteBuffer; import java.util.ArrayList; @@ -141,7 +143,7 @@ public class TextureBlenderAWT extends AbstractTextureBlender { dataArray.add(newData); } - Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); + Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, ColorSpace.Linear) : new Image(Format.RGBA8, width, height, dataArray.get(0), ColorSpace.Linear); if (image.getMipMapSizes() != null) { result.setMipMapSizes(image.getMipMapSizes().clone()); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java index 264ebc1da..b40e2cc8f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java @@ -6,9 +6,12 @@ import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; + import java.nio.ByteBuffer; import java.util.ArrayList; + import jme3tools.converters.RGB565; /** @@ -119,7 +122,7 @@ public class TextureBlenderDDS extends TextureBlenderAWT { dataArray.add(newData); } - Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0)); + Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray, ColorSpace.Linear) : new Image(format, width, height, dataArray.get(0), ColorSpace.Linear); if (image.getMipMapSizes() != null) { result.setMipMapSizes(image.getMipMapSizes().clone()); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java index cbfc28631..1f2ae01e1 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java @@ -7,7 +7,9 @@ import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.logging.Level; @@ -93,7 +95,7 @@ public class TextureBlenderLuminance extends AbstractTextureBlender { dataArray.add(newData); } - Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); + Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, ColorSpace.Linear) : new Image(Format.RGBA8, width, height, dataArray.get(0), ColorSpace.Linear); if (image.getMipMapSizes() != null) { result.setMipMapSizes(image.getMipMapSizes().clone()); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java index efacee639..569c328be 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java @@ -17,12 +17,18 @@ import jme3tools.converters.RGB565; case RGBA8: pixel.fromARGB8(data.get(index + 3), data.get(index), data.get(index + 1), data.get(index + 2)); break; + case ARGB8: + pixel.fromARGB8(data.get(index), data.get(index + 1), data.get(index + 2), data.get(index + 3)); + break; case ABGR8: pixel.fromARGB8(data.get(index), data.get(index + 3), data.get(index + 2), data.get(index + 1)); break; case BGR8: pixel.fromARGB8((byte) 0xFF, data.get(index + 2), data.get(index + 1), data.get(index)); break; + case BGRA8: + pixel.fromARGB8(data.get(index + 3), data.get(index + 2), data.get(index + 1), data.get(index)); + break; case RGB8: pixel.fromARGB8((byte) 0xFF, data.get(index), data.get(index + 1), data.get(index + 2)); break; @@ -72,6 +78,12 @@ import jme3tools.converters.RGB565; data.put(index + 2, pixel.getB8()); data.put(index + 3, pixel.getA8()); break; + case ARGB8: + data.put(index, pixel.getA8()); + data.put(index + 1, pixel.getR8()); + data.put(index + 2, pixel.getG8()); + data.put(index + 3, pixel.getB8()); + break; case ABGR8: data.put(index, pixel.getA8()); data.put(index + 1, pixel.getB8()); @@ -83,6 +95,12 @@ import jme3tools.converters.RGB565; data.put(index + 1, pixel.getG8()); data.put(index + 2, pixel.getR8()); break; + case BGRA8: + data.put(index, pixel.getB8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getR8()); + data.put(index + 3, pixel.getA8()); + break; case RGB8: data.put(index, pixel.getR8()); data.put(index + 1, pixel.getG8()); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java index 916090228..47cf7090b 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java @@ -26,7 +26,9 @@ public class PixelIOFactory { case ABGR8: case RGBA8: case BGR8: + case BGRA8: case RGB8: + case ARGB8: case RGB111110F: case RGB16F: case RGB16F_to_RGB111110F: From 15ec285b1a8db3bcf9e37e081add056983760432 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Wed, 25 Mar 2015 21:59:48 +0100 Subject: [PATCH 016/225] Feature: added support for linear and constant interpolation types for ipo curves. --- .../plugins/blender/curves/BezierCurve.java | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java index 9876702c8..96a91335d 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java @@ -1,10 +1,11 @@ package com.jme3.scene.plugins.blender.curves; +import java.util.ArrayList; +import java.util.List; + import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Structure; -import java.util.ArrayList; -import java.util.List; /** * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize @@ -12,21 +13,26 @@ import java.util.List; * @author Marcin Roguski (Kaelthas) */ public class BezierCurve { + private static final int IPO_CONSTANT = 0; + private static final int IPO_LINEAR = 1; + private static final int IPO_BEZIER = 2; - public static final int X_VALUE = 0; - public static final int Y_VALUE = 1; - public static final int Z_VALUE = 2; + public static final int X_VALUE = 0; + public static final int Y_VALUE = 1; + public static final int Z_VALUE = 2; /** * The type of the curve. Describes the data it modifies. * Used in ipos calculations. */ - private int type; + private int type; /** The dimension of the curve. */ - private int dimension; + private int dimension; /** A table of the bezier points. */ - private double[][][] bezierPoints; + private double[][][] bezierPoints; /** Array that stores a radius for each bezier triple. */ - private double[] radiuses; + private double[] radiuses; + /** Interpolation types of the bezier triples. */ + private int[] interpolations; public BezierCurve(final int type, final List bezTriples, final int dimension) { this(type, bezTriples, dimension, false); @@ -44,6 +50,7 @@ public class BezierCurve { // the third index specifies the coordinates of the specific point in a bezier triple bezierPoints = new double[bezTriples.size()][3][dimension]; radiuses = new double[bezTriples.size()]; + interpolations = new int[bezTriples.size()]; int i = 0, j, k; for (Structure bezTriple : bezTriples) { DynamicArray vec = (DynamicArray) bezTriple.getFieldValue("vec"); @@ -57,7 +64,8 @@ public class BezierCurve { bezierPoints[i][j][1] = temp; } } - radiuses[i++] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); + radiuses[i] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); + interpolations[i++] = ((Number) bezTriple.getFieldValue("ipo", IPO_BEZIER)).intValue(); } } @@ -75,10 +83,19 @@ public class BezierCurve { for (int i = 0; i < bezierPoints.length - 1; ++i) { if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) { double t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]); - double oneMinusT = 1.0f - t; - double oneMinusT2 = oneMinusT * oneMinusT; - double t2 = t * t; - return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; + switch (interpolations[i]) { + case IPO_BEZIER: + double oneMinusT = 1.0f - t; + double oneMinusT2 = oneMinusT * oneMinusT; + double t2 = t * t; + return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; + case IPO_LINEAR: + return (1f - t) * bezierPoints[i][1][valuePart] + t * bezierPoints[i + 1][1][valuePart]; + case IPO_CONSTANT: + return bezierPoints[i][1][valuePart]; + default: + throw new IllegalStateException("Unknown interpolation type for curve: " + interpolations[i]); + } } } if (frame < bezierPoints[0][1][0]) { From 25da36e5907b79f2b538d86cdaa56e8aa90e4834 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 26 Mar 2015 17:02:01 -0400 Subject: [PATCH 017/225] jme3-bullet-native: copyBinaryToLibs to depend on building native lib --- jme3-bullet-native/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-bullet-native/build.gradle b/jme3-bullet-native/build.gradle index 163485f0e..dbb74981a 100644 --- a/jme3-bullet-native/build.gradle +++ b/jme3-bullet-native/build.gradle @@ -175,7 +175,7 @@ binaries.withType(SharedLibraryBinary) { binary -> // Add depend on build jar.dependsOn builderTask // Add output to libs folder - task "copyBinaryToLibs${targetPlatform}"(type: Copy) { + task "copyBinaryToLibs${targetPlatform}"(type: Copy, dependsOn: builderTask) { from builderTask.outputFile into "libs/native/${targetPlatform.operatingSystem.name}/${targetPlatform.architecture.name}" } From 1076b489abbe3ecb5b3db97fd4783d7e78e18358 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 26 Mar 2015 17:03:04 -0400 Subject: [PATCH 018/225] GLRenderer: remove check FB error calls as it causes GPU stall --- .../com/jme3/renderer/opengl/GLRenderer.java | 77 +++++-------------- 1 file changed, 21 insertions(+), 56 deletions(-) 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 9d080384a..9ae1c8cc7 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 @@ -1321,13 +1321,6 @@ public class GLRenderer implements Renderer { glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, prevFBO); - try { - checkFrameBufferError(); - } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, "Source FBO:\n{0}", src); - logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); - throw ex; - } } else { throw new RendererException("Framebuffer blitting not supported by the video hardware"); } @@ -1488,6 +1481,8 @@ public class GLRenderer implements Renderer { updateFrameBufferAttachment(fb, colorBuf); } + checkFrameBufferError(); + fb.clearUpdateNeeded(); } @@ -1645,8 +1640,6 @@ public class GLRenderer implements Renderer { assert context.boundFBO == fb.getId(); context.boundFB = fb; - - checkFrameBufferError(); } } @@ -2221,53 +2214,25 @@ public class GLRenderer implements Renderer { int usage = convertUsage(vb.getUsage()); vb.getData().rewind(); -// if (created || vb.hasDataSizeChanged()) { - // upload data based on format - switch (vb.getFormat()) { - case Byte: - case UnsignedByte: - gl.glBufferData(target, (ByteBuffer) vb.getData(), usage); - break; - // case Half: - case Short: - case UnsignedShort: - gl.glBufferData(target, (ShortBuffer) vb.getData(), usage); - break; - case Int: - case UnsignedInt: - glext.glBufferData(target, (IntBuffer) vb.getData(), usage); - break; - case Float: - gl.glBufferData(target, (FloatBuffer) vb.getData(), usage); - break; - default: - throw new UnsupportedOperationException("Unknown buffer format."); - } -// } else { -// // Invalidate buffer data (orphan) before uploading new data. -// switch (vb.getFormat()) { -// case Byte: -// case UnsignedByte: -// gl.glBufferSubData(target, 0, (ByteBuffer) vb.getData()); -// break; -// case Short: -// case UnsignedShort: -// gl.glBufferSubData(target, 0, (ShortBuffer) vb.getData()); -// break; -// case Int: -// case UnsignedInt: -// gl.glBufferSubData(target, 0, (IntBuffer) vb.getData()); -// break; -// case Float: -// gl.glBufferSubData(target, 0, (FloatBuffer) vb.getData()); -// break; -// case Double: -// gl.glBufferSubData(target, 0, (DoubleBuffer) vb.getData()); -// break; -// default: -// throw new UnsupportedOperationException("Unknown buffer format."); -// } -// } + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + gl.glBufferData(target, (ByteBuffer) vb.getData(), usage); + break; + case Short: + case UnsignedShort: + gl.glBufferData(target, (ShortBuffer) vb.getData(), usage); + break; + case Int: + case UnsignedInt: + glext.glBufferData(target, (IntBuffer) vb.getData(), usage); + break; + case Float: + gl.glBufferData(target, (FloatBuffer) vb.getData(), usage); + break; + default: + throw new UnsupportedOperationException("Unknown buffer format."); + } vb.clearUpdateNeeded(); } From 31835366b2f3e833f34f701f00c780571ded78bf Mon Sep 17 00:00:00 2001 From: rainmantsr Date: Fri, 27 Mar 2015 20:37:23 +0100 Subject: [PATCH 019/225] Add: sweepTest to jme3-bullet-native --- .../cpp/com_jme3_bullet_PhysicsSpace.cpp | 62 +++++++++++++ .../native/cpp/com_jme3_bullet_PhysicsSpace.h | 9 ++ .../src/native/cpp/jmeBulletUtil.cpp | 90 +++++++++++++++++++ .../src/native/cpp/jmeBulletUtil.h | 3 + .../src/native/cpp/jmeClasses.cpp | 79 ++++++++++++++++ .../src/native/cpp/jmeClasses.h | 12 +++ .../java/com/jme3/bullet/PhysicsSpace.java | 59 ++++++------ 7 files changed, 286 insertions(+), 28 deletions(-) diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp index b51630c55..32bc249dd 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp @@ -468,6 +468,68 @@ extern "C" { return; } + + + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native + (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) { + + jmePhysicsSpace* space = reinterpret_cast (spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + + btCollisionShape* shape = reinterpret_cast (shapeId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The shape does not exist."); + return; + } + + struct AllConvexResultCallback : public btCollisionWorld::ConvexResultCallback { + + AllConvexResultCallback(const btTransform& convexFromWorld, const btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) { + } + jobject resultlist; + JNIEnv* env; + btTransform m_convexFromWorld; //used to calculate hitPointWorld from hitFraction + btTransform m_convexToWorld; + + btVector3 m_hitNormalWorld; + btVector3 m_hitPointWorld; + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { + if (normalInWorldSpace) { + m_hitNormalWorld = convexResult.m_hitNormalLocal; + } + else { + m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal; + } + m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction); + + jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject); + + return 1.f; + } + }; + + btTransform native_to = btTransform(); + jmeBulletUtil::convert(env, to, &native_to); + + btTransform native_from = btTransform(); + jmeBulletUtil::convert(env, from, &native_from); + + btScalar native_allowed_ccd_penetration = btScalar(allowedCcdPenetration); + + + AllConvexResultCallback resultCallback(native_from, native_to); + resultCallback.env = env; + resultCallback.resultlist = resultlist; + space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration); + return; + } + #ifdef __cplusplus } #endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h index e040f8da8..b499ff04c 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h @@ -165,6 +165,15 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative (JNIEnv *, jobject, jlong); + +/* +* Class: com_jme3_bullet_PhysicsSpace +* Method : sweepTest_native +* Signature: (J;L;Lcom/jme3/math/Transform;Lcom/jme3/math/Transform;L;JLjava/util/List;F)V +*/ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native +(JNIEnv *, jobject, jlong, jobject, jobject, jlong, jobject, jfloat); + #ifdef __cplusplus } #endif diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp index fa88ad473..04b56a545 100644 --- a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp +++ b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp @@ -59,6 +59,38 @@ void jmeBulletUtil::convert(JNIEnv* env, jobject in, btVector3* out) { out->setZ(z); } +void jmeBulletUtil::convert(JNIEnv* env, jobject in, btQuaternion* out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + float x = env->GetFloatField(in, jmeClasses::Quaternion_x); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float y = env->GetFloatField(in, jmeClasses::Quaternion_y); //env->CallFloatMethod(in, jmeClasses::Vector3f_getY); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float z = env->GetFloatField(in, jmeClasses::Quaternion_z); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + float w = env->GetFloatField(in, jmeClasses::Quaternion_w); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + out->setX(x); + out->setY(y); + out->setZ(z); + out->setW(w); +} + + void jmeBulletUtil::convert(JNIEnv* env, const btVector3* in, jobject out) { if (in == NULL || out == NULL) { jmeClasses::throwNPE(env); @@ -325,3 +357,61 @@ void jmeBulletUtil::addResult(JNIEnv* env, jobject resultlist, btVector3* hitnor return; } } + + +void jmeBulletUtil::addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, btScalar m_hitFraction, const btCollisionObject* hitobject) { + + jobject singleresult = env->AllocObject(jmeClasses::PhysicsSweep_Class); + jobject hitnormalvec = env->AllocObject(jmeClasses::Vector3f); + + convert(env, hitnormal, hitnormalvec); + jmeUserPointer *up1 = (jmeUserPointer*)hitobject->getUserPointer(); + + env->SetObjectField(singleresult, jmeClasses::PhysicsSweep_normalInWorldSpace, hitnormalvec); + env->SetFloatField(singleresult, jmeClasses::PhysicsSweep_hitfraction, m_hitFraction); + + env->SetObjectField(singleresult, jmeClasses::PhysicsSweep_collisionObject, up1->javaCollisionObject); + env->CallVoidMethod(resultlist, jmeClasses::PhysicsSweep_addmethod, singleresult); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} + +void jmeBulletUtil::convert(JNIEnv* env, jobject in, btTransform* out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + + jobject translation_vec = env->CallObjectMethod(in, jmeClasses::Transform_translation); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + jobject rot_quat = env->CallObjectMethod(in, jmeClasses::Transform_rotation); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + /* + //Scale currently not supported by bullet + //@TBD: Create an assertion somewhere to avoid scale values + jobject scale_vec = env->GetObjectField(in, jmeClasses::Transform_scale); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + */ + btVector3 native_translation_vec = btVector3(); + //btVector3 native_scale_vec = btVector3(); + btQuaternion native_rot_quat = btQuaternion(); + + convert(env, translation_vec, &native_translation_vec); + //convert(env, scale_vec, native_scale_vec); + convert(env, rot_quat, &native_rot_quat); + + out->setRotation(native_rot_quat); + out->setOrigin(native_translation_vec); +} diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h index 96509ab9e..fa09d09cc 100644 --- a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h +++ b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h @@ -42,10 +42,13 @@ public: static void convert(JNIEnv* env, jobject in, btVector3* out); static void convert(JNIEnv* env, const btVector3* in, jobject out); static void convert(JNIEnv* env, jobject in, btMatrix3x3* out); + static void convert(JNIEnv* env, jobject in, btQuaternion* out); static void convert(JNIEnv* env, const btMatrix3x3* in, jobject out); static void convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out); static void convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out); + static void convert(JNIEnv* env, jobject in, btTransform* out); static void addResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld,const btScalar m_hitFraction,const btCollisionObject* hitobject); + static void addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, const btScalar m_hitFraction, const btCollisionObject* hitobject); private: jmeBulletUtil(){}; ~jmeBulletUtil(){}; diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp index e0b5515b4..6b7caa0e9 100644 --- a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp +++ b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp @@ -91,6 +91,21 @@ jfieldID jmeClasses::PhysicsRay_collisionObject; jclass jmeClasses::PhysicsRay_listresult; jmethodID jmeClasses::PhysicsRay_addmethod; +jclass jmeClasses::PhysicsSweep_Class; +jmethodID jmeClasses::PhysicsSweep_newSingleResult; + +jfieldID jmeClasses::PhysicsSweep_normalInWorldSpace; +jfieldID jmeClasses::PhysicsSweep_hitfraction; +jfieldID jmeClasses::PhysicsSweep_collisionObject; + +jclass jmeClasses::PhysicsSweep_listresult; +jmethodID jmeClasses::PhysicsSweep_addmethod; + + +jclass jmeClasses::Transform; +jmethodID jmeClasses::Transform_rotation; +jmethodID jmeClasses::Transform_translation; + //private fields //JNIEnv* jmeClasses::env; JavaVM* jmeClasses::vm; @@ -240,6 +255,70 @@ void jmeClasses::initJavaClasses(JNIEnv* env) { env->Throw(env->ExceptionOccurred()); return; } + + PhysicsSweep_Class = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/collision/PhysicsSweepTestResult")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; +} + + PhysicsSweep_newSingleResult = env->GetMethodID(PhysicsSweep_Class, "", "()V"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsSweep_normalInWorldSpace = env->GetFieldID(PhysicsSweep_Class, "hitNormalLocal", "Lcom/jme3/math/Vector3f;"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + + PhysicsSweep_hitfraction = env->GetFieldID(PhysicsSweep_Class, "hitFraction", "F"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + + PhysicsSweep_collisionObject = env->GetFieldID(PhysicsSweep_Class, "collisionObject", "Lcom/jme3/bullet/collision/PhysicsCollisionObject;"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsSweep_listresult = env->FindClass("java/util/List"); + PhysicsSweep_listresult = (jclass)env->NewGlobalRef(PhysicsSweep_listresult); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsSweep_addmethod = env->GetMethodID(PhysicsSweep_listresult, "add", "(Ljava/lang/Object;)Z"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + Transform = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Transform")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + Transform_rotation = env->GetMethodID(Transform, "getRotation", "()Lcom/jme3/math/Quaternion;"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + Transform_translation = env->GetMethodID(Transform, "getTranslation", "()Lcom/jme3/math/Vector3f;"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + } void jmeClasses::throwNPE(JNIEnv* env) { diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.h b/jme3-bullet-native/src/native/cpp/jmeClasses.h index 24684dae9..bb1b0e99a 100644 --- a/jme3-bullet-native/src/native/cpp/jmeClasses.h +++ b/jme3-bullet-native/src/native/cpp/jmeClasses.h @@ -89,6 +89,18 @@ public: static jclass PhysicsRay_listresult; static jmethodID PhysicsRay_addmethod; + static jclass PhysicsSweep_Class; + static jmethodID PhysicsSweep_newSingleResult; + static jfieldID PhysicsSweep_normalInWorldSpace; + static jfieldID PhysicsSweep_hitfraction; + static jfieldID PhysicsSweep_collisionObject; + static jclass PhysicsSweep_listresult; + static jmethodID PhysicsSweep_addmethod; + + static jclass Transform; + static jmethodID Transform_rotation; + static jmethodID Transform_translation; + static jclass DebugMeshCallback; static jmethodID DebugMeshCallback_addVector; diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 0f011f8d7..5aa4f6abf 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -820,6 +820,10 @@ public class PhysicsSpace { // return lrr.hitFraction; // } // } +// +// + + /** * Performs a sweep collision test and returns the results as a list of * PhysicsSweepTestResults
You have to use different Transforms for @@ -828,16 +832,16 @@ public class PhysicsSpace { * center. */ public List sweepTest(CollisionShape shape, Transform start, Transform end) { - List results = new LinkedList(); -// if (!(shape.getCShape() instanceof ConvexShape)) { -// logger.log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!"); -// return results; -// } -// dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results)); - return results; + List results = new LinkedList(); + sweepTest(shape, start, end , results); + return (List) results; + } + public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { + return sweepTest(shape, start, end, results, 0.0f); } + public native void sweepTest_native(long shape, Transform from, Transform to, long physicsSpaceId, List results, float allowedCcdPenetration); /** * Performs a sweep collision test and returns the results as a list of * PhysicsSweepTestResults
You have to use different Transforms for @@ -845,31 +849,30 @@ public class PhysicsSpace { * collision if it starts INSIDE an object and is moving AWAY from its * center. */ - public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { + public List sweepTest(CollisionShape shape, Transform start, Transform end, List results, float allowedCcdPenetration ) { results.clear(); -// if (!(shape.getCShape() instanceof ConvexShape)) { -// logger.log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!"); -// return results; -// } -// dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results)); + sweepTest_native(shape.getObjectId(), start, end, physicsSpaceId, results, allowedCcdPenetration); return results; } -// private class InternalSweepListener extends CollisionWorld.ConvexResultCallback { -// -// private List results; -// -// public InternalSweepListener(List results) { -// this.results = results; -// } -// -// @Override -// public float addSingleResult(LocalConvexResult lcr, boolean bln) { -// PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer(); -// results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln)); -// return lcr.hitFraction; -// } -// } +/* private class InternalSweepListener extends CollisionWorld.ConvexResultCallback { + + private List results; + + public InternalSweepListener(List results) { + this.results = results; + } + + @Override + public float addSingleResult(LocalConvexResult lcr, boolean bln) { + PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer(); + results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln)); + return lcr.hitFraction; + } + } + + */ + /** * destroys the current PhysicsSpace so that a new one can be created */ From 400c09a6331dff96076690fe3767e331ed702f73 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:32:52 -0400 Subject: [PATCH 020/225] jme3-core: fix minor issues pointed in static analysis --- .../main/java/com/jme3/asset/AssetConfig.java | 2 +- .../com/jme3/asset/DesktopAssetManager.java | 5 ++--- .../main/java/com/jme3/asset/ImplHandler.java | 6 +++--- .../main/java/com/jme3/light/SpotLight.java | 2 +- .../main/java/com/jme3/scene/BatchNode.java | 2 +- .../src/main/java/com/jme3/scene/Node.java | 2 +- .../jme3/scene/instancing/InstancedNode.java | 2 +- .../main/java/com/jme3/shader/DefineList.java | 2 +- .../java/com/jme3/texture/FrameBuffer.java | 7 ++++--- .../src/main/java/com/jme3/util/IntMap.java | 3 ++- .../jme3/util/TangentBinormalGenerator.java | 18 +++++++----------- .../util/blockparser/BlockLanguageParser.java | 2 +- .../java/com/jme3/audio/plugins/WAVLoader.java | 2 +- .../com/jme3/export/binary/BinaryImporter.java | 4 +--- 14 files changed, 27 insertions(+), 32 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java index 50b6e38af..71c4d8bb1 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java @@ -69,7 +69,7 @@ public final class AssetConfig { public static void loadText(AssetManager assetManager, URL configUrl) throws IOException{ InputStream in = configUrl.openStream(); try { - Scanner scan = new Scanner(in); + Scanner scan = new Scanner(in, "UTF-8"); scan.useLocale(Locale.US); // Fix commas / periods ?? while (scan.hasNext()){ String cmd = scan.next(); diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java index 4364f3b60..66a6fbbdf 100644 --- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java @@ -312,13 +312,12 @@ public class DesktopAssetManager implements AssetManager { protected T registerAndCloneSmartAsset(AssetKey key, T obj, AssetProcessor proc, AssetCache cache) { // object obj is the original asset // create an instance for user - T clone = (T) obj; if (proc == null) { throw new IllegalStateException("Asset implements " + "CloneableSmartAsset but doesn't " + "have processor to handle cloning"); } else { - clone = (T) proc.createClone(obj); + T clone = (T) proc.createClone(obj); if (cache != null && clone != obj) { cache.registerAssetClone(key, clone); } else { @@ -326,8 +325,8 @@ public class DesktopAssetManager implements AssetManager { + "CloneableSmartAsset but doesn't have cache or " + "was not cloned"); } + return clone; } - return clone; } @Override diff --git a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java index 3601bc463..73e6d8df3 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java +++ b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java @@ -47,7 +47,7 @@ import java.util.logging.Logger; * This is done by keeping an instance of each asset loader and asset * locator object in a thread local. */ -public class ImplHandler { +final class ImplHandler { private static final Logger logger = Logger.getLogger(ImplHandler.class.getName()); @@ -75,7 +75,7 @@ public class ImplHandler { this.assetManager = assetManager; } - protected class ImplThreadLocal extends ThreadLocal { + protected static class ImplThreadLocal extends ThreadLocal { private final Class type; private final String path; @@ -83,7 +83,7 @@ public class ImplHandler { public ImplThreadLocal(Class type, String[] extensions){ this.type = type; - this.extensions = extensions; + this.extensions = extensions.clone(); this.path = null; } diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index 0499ce254..f02f76fa4 100644 --- a/jme3-core/src/main/java/com/jme3/light/SpotLight.java +++ b/jme3-core/src/main/java/com/jme3/light/SpotLight.java @@ -56,7 +56,7 @@ import java.io.IOException; * the light intensity slowly decrease between the inner cone and the outer cone. * @author Nehon */ -public class SpotLight extends Light implements Savable { +public class SpotLight extends Light { protected Vector3f position = new Vector3f(); protected Vector3f direction = new Vector3f(0,-1,0); diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 8cfed4244..475cfdf71 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -64,7 +64,7 @@ import java.util.logging.Logger; * TODO more automagic (batch when needed in the updateLogicalState) * @author Nehon */ -public class BatchNode extends GeometryGroupNode implements Savable { +public class BatchNode extends GeometryGroupNode { private static final Logger logger = Logger.getLogger(BatchNode.class.getName()); /** diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index 816140e21..5edfa021b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -58,7 +58,7 @@ import java.util.logging.Logger; * @author Gregg Patton * @author Joshua Slack */ -public class Node extends Spatial implements Savable { +public class Node extends Spatial { private static final Logger logger = Logger.getLogger(Node.class.getName()); diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java index b3cb26da0..554b8606a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -57,7 +57,7 @@ public class InstancedNode extends GeometryGroupNode { setGeometryStartIndex(geom, startIndex); } - private static class InstanceTypeKey implements Cloneable { + private static final class InstanceTypeKey implements Cloneable { Mesh mesh; Material material; diff --git a/jme3-core/src/main/java/com/jme3/shader/DefineList.java b/jme3-core/src/main/java/com/jme3/shader/DefineList.java index 93d4cbeaf..dd605fc7e 100644 --- a/jme3-core/src/main/java/com/jme3/shader/DefineList.java +++ b/jme3-core/src/main/java/com/jme3/shader/DefineList.java @@ -40,7 +40,7 @@ import java.io.IOException; import java.util.Map; import java.util.TreeMap; -public class DefineList implements Savable, Cloneable { +public final class DefineList implements Savable, Cloneable { private static final String ONE = "1"; diff --git a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java index ed053339c..bc21fd82b 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -72,9 +72,10 @@ import java.util.ArrayList; * @author Kirill Vainer */ public class FrameBuffer extends NativeObject { - public static int SLOT_UNDEF = -1; - public static int SLOT_DEPTH = -100; - public static int SLOT_DEPTH_STENCIL = -101; + + public static final int SLOT_UNDEF = -1; + public static final int SLOT_DEPTH = -100; + public static final int SLOT_DEPTH_STENCIL = -101; private int width = 0; private int height = 0; diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java index fed91be06..38ffb2431 100644 --- a/jme3-core/src/main/java/com/jme3/util/IntMap.java +++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java @@ -34,6 +34,7 @@ package com.jme3.util; import com.jme3.util.IntMap.Entry; import java.util.Iterator; import java.util.Map; +import java.util.NoSuchElementException; /** * Similar to a {@link Map} except that ints are used as keys. @@ -234,7 +235,7 @@ public final class IntMap implements Iterable>, Cloneable { public Entry next() { if (el >= size) - throw new IllegalStateException("No more elements!"); + throw new NoSuchElementException("No more elements!"); if (cur != null){ Entry e = cur; diff --git a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java index ae82b3422..0f9aac1fc 100644 --- a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java @@ -274,11 +274,9 @@ public class TangentBinormalGenerator { triData.setIndex(index); triData.triangleOffset = i * 3 ; } - if (triData != null) { - vertices.get(index[0]).triangles.add(triData); - vertices.get(index[1]).triangles.add(triData); - vertices.get(index[2]).triangles.add(triData); - } + vertices.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); } return vertices; @@ -483,7 +481,7 @@ public class TangentBinormalGenerator { boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]); TriangleData triData = processTriangle(index, v, t); - if (triData != null && !isDegenerate) { + if (!isDegenerate) { vertices.get(index[0]).triangles.add(triData); vertices.get(index[1]).triangles.add(triData); vertices.get(index[2]).triangles.add(triData); @@ -529,11 +527,9 @@ public class TangentBinormalGenerator { populateFromBuffer(t[2], textureBuffer, index[2]); TriangleData triData = processTriangle(index, v, t); - if (triData != null) { - vertices.get(index[0]).triangles.add(triData); - vertices.get(index[1]).triangles.add(triData); - vertices.get(index[2]).triangles.add(triData); - } + vertices.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); Vector3f vTemp = v[1]; v[1] = v[2]; diff --git a/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java index 16968aad2..7718a0f4b 100644 --- a/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java @@ -71,7 +71,7 @@ public class BlockLanguageParser { private void load(InputStream in) throws IOException{ reset(); - reader = new InputStreamReader(in); + reader = new InputStreamReader(in, "UTF-8"); StringBuilder buffer = new StringBuilder(); boolean insideComment = false; diff --git a/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java index 098de7a15..997a8f4fb 100644 --- a/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java @@ -196,7 +196,7 @@ public class WAVLoader implements AssetLoader { break; case i_data: // Compute duration based on data chunk size - duration = len / bytesPerSec; + duration = (float)(len / bytesPerSec); if (readStream) { readDataChunkForStream(inOffset, len); diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java index 933bee721..b34adbca6 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java @@ -270,9 +270,7 @@ public final class BinaryImporter implements JmeImporter { try { return load(fis, listener); } finally { - if (fis != null) { - fis.close(); - } + fis.close(); } } From 2bc55451713875a9dfcfae044016c752fc7f2e1b Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:36:43 -0400 Subject: [PATCH 021/225] OGLESShaderRenderer: remove clearTextureUnits since it doesnt do anything --- .../renderer/android/OGLESShaderRenderer.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java index cef3e9f66..462715f37 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java @@ -1850,21 +1850,6 @@ public class OGLESShaderRenderer implements Renderer { TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); } - public void clearTextureUnits() { - IDList textureList = context.textureIndexList; - Image[] textures = context.boundTextures; - for (int i = 0; i < textureList.oldLen; i++) { - int idx = textureList.oldList[i]; -// if (context.boundTextureUnit != idx){ -// glActiveTexture(GL_TEXTURE0 + idx); -// context.boundTextureUnit = idx; -// } -// glDisable(convertTextureType(textures[idx].getType())); - textures[idx] = null; - } - context.textureIndexList.copyNewToOld(); - } - public void deleteImage(Image image) { int texId = image.getId(); if (texId != -1) { @@ -2339,7 +2324,6 @@ public class OGLESShaderRenderer implements Renderer { RendererUtil.checkGLError(); } clearVertexAttribs(); - clearTextureUnits(); } private void renderMeshDefault(Mesh mesh, int lod, int count) { @@ -2378,7 +2362,6 @@ public class OGLESShaderRenderer implements Renderer { RendererUtil.checkGLError(); } clearVertexAttribs(); - clearTextureUnits(); } public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { From 4f15fa314765ac312c4b719f0a29061f118cfeb0 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:37:50 -0400 Subject: [PATCH 022/225] AnimChannel: remove useless println --- jme3-core/src/main/java/com/jme3/animation/AnimChannel.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java index 4c362ff00..44b836115 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java @@ -312,7 +312,6 @@ public final class AnimChannel { } } animation = null; - // System.out.println("Setting notified false"); notified = false; } From f0fbdffb85f520fb3af8d99c08356d8b0a3162c0 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:38:14 -0400 Subject: [PATCH 023/225] BoundingSphere: remove useless null check --- jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java index 30751e078..d5dd56203 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java @@ -640,8 +640,7 @@ public class BoundingSphere extends BoundingVolume { return rVal; } - return new BoundingSphere(radius, - (center != null ? (Vector3f) center.clone() : null)); + return new BoundingSphere(radius, center.clone()); } /** From fdf050c13d52a3d8b639388c26c68c99fc8e4cdc Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:38:56 -0400 Subject: [PATCH 024/225] J3MLoader: set texture name in addition to key when loading it --- .../src/plugins/java/com/jme3/material/plugins/J3MLoader.java | 1 + 1 file changed, 1 insertion(+) 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 449802161..5d7f5ca8c 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 @@ -175,6 +175,7 @@ public class J3MLoader implements AssetLoader { tex.setWrap(WrapMode.Repeat); } tex.setKey(texKey); + tex.setName(texKey.getName()); } return tex; }else{ From a683fbb16c8cc43e115d8b9d24439c16eee30a97 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:39:27 -0400 Subject: [PATCH 025/225] Texture: allow setting aniso = 0, since that's the default anyway --- jme3-core/src/main/java/com/jme3/texture/Texture.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture.java b/jme3-core/src/main/java/com/jme3/texture/Texture.java index b41d2ac10..582d06565 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture.java @@ -488,7 +488,8 @@ public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable /** * @return the anisotropic filtering level for this texture. Default value - * is 1 (no anisotrophy), 2 means x2, 4 is x4, etc. + * is 0 (use value from config), + * 1 means 1x (no anisotrophy), 2 means x2, 4 is x4, etc. */ public int getAnisotropicFilter() { return anisotropicFilter; @@ -499,11 +500,7 @@ public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable * the anisotropic filtering level for this texture. */ public void setAnisotropicFilter(int level) { - if (level < 1) { - anisotropicFilter = 1; - } else { - anisotropicFilter = level; - } + anisotropicFilter = Math.max(0, level); } @Override From 068047200e22ff391567e655010c19474ace057b Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:43:32 -0400 Subject: [PATCH 026/225] Threads: standardize names. Make sure they all start with "jME3". --- .../main/java/com/jme3/app/state/VideoRecorderAppState.java | 2 +- .../src/main/java/com/jme3/audio/openal/ALAudioRenderer.java | 5 ++++- jme3-core/src/main/java/com/jme3/system/NullContext.java | 4 +++- .../main/java/com/jme3/app/state/VideoRecorderAppState.java | 2 +- .../src/main/java/com/jme3/system/jogl/JoglContext.java | 2 ++ .../main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java | 2 +- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 4 ++-- .../src/main/java/com/jme3/system/lwjgl/LwjglContext.java | 2 ++ .../src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java | 2 +- .../java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java | 2 +- .../java/com/jme3/terrain/geomipmap/TerrainLodControl.java | 2 +- 11 files changed, 19 insertions(+), 10 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java b/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java index c915164b3..a388ad612 100644 --- a/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java +++ b/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java @@ -74,7 +74,7 @@ public class VideoRecorderAppState extends AbstractAppState { public Thread newThread(Runnable r) { Thread th = new Thread(r); - th.setName("jME Video Processing Thread"); + th.setName("jME3 Video Processor"); th.setDaemon(true); return th; } diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java index 346998e2e..c3ccea741 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java @@ -51,6 +51,9 @@ import static com.jme3.audio.openal.EFX.*; public class ALAudioRenderer implements AudioRenderer, Runnable { private static final Logger logger = Logger.getLogger(ALAudioRenderer.class.getName()); + + private static final String THREAD_NAME = "jME3 Audio Decoder"; + private final NativeObjectManager objManager = new NativeObjectManager(); // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 // which is exactly 1 second of audio. @@ -75,7 +78,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { // Fill streaming sources every 50 ms private static final float UPDATE_RATE = 0.05f; - private final Thread decoderThread = new Thread(this, "jME3 Audio Decoding Thread"); + private final Thread decoderThread = new Thread(this, THREAD_NAME); private final Object threadLock = new Object(); private final AL al; diff --git a/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index f7d4b3d5a..41204e6c9 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -46,6 +46,8 @@ public class NullContext implements JmeContext, Runnable { protected static final Logger logger = Logger.getLogger(NullContext.class.getName()); + protected static final String THREAD_NAME = "jME3 Headless Main"; + protected AtomicBoolean created = new AtomicBoolean(false); protected AtomicBoolean needClose = new AtomicBoolean(false); protected final Object createdLock = new Object(); @@ -150,7 +152,7 @@ public class NullContext implements JmeContext, Runnable { return; } - new Thread(this, "Headless Application Thread").start(); + new Thread(this, THREAD_NAME).start(); if (waitFor) waitFor(true); } diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java index 9466b4a50..ccaecc580 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java +++ b/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java @@ -71,7 +71,7 @@ public class VideoRecorderAppState extends AbstractAppState { public Thread newThread(Runnable r) { Thread th = new Thread(r); - th.setName("jME Video Processing Thread"); + th.setName("jME3 Video Processor"); th.setDaemon(true); return th; } diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java index eca36d84b..8861904b8 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java @@ -55,6 +55,8 @@ public abstract class JoglContext implements JmeContext { private static final Logger logger = Logger.getLogger(JoglContext.class.getName()); + protected static final String THREAD_NAME = "jME3 Main"; + protected AtomicBoolean created = new AtomicBoolean(false); protected AtomicBoolean renderable = new AtomicBoolean(false); protected final Object createdLock = new Object(); diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java index 505808878..e2ad1f79a 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java @@ -146,7 +146,7 @@ public class JoglOffscreenBuffer extends JoglContext implements Runnable { return; } - new Thread(this, "JOGL Renderer Thread").start(); + new Thread(this, THREAD_NAME).start(); if (waitFor) { waitFor(true); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index f1fcc34a2..2452e470b 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -96,7 +96,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex canvas.setFocusable(true); canvas.setIgnoreRepaint(true); - renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread = new Thread(LwjglCanvas.this, THREAD_NAME); renderThread.start(); }else if (needClose.get()){ return; @@ -162,7 +162,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex if (renderThread == null){ logger.log(Level.FINE, "MAIN: Creating OGL thread."); - renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread = new Thread(LwjglCanvas.this, THREAD_NAME); renderThread.start(); } // do not do anything. 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 e318d24a2..1286323ef 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 @@ -67,6 +67,8 @@ public abstract class LwjglContext implements JmeContext { private static final Logger logger = Logger.getLogger(LwjglContext.class.getName()); + protected static final String THREAD_NAME = "jME3 Main"; + protected AtomicBoolean created = new AtomicBoolean(false); protected AtomicBoolean renderable = new AtomicBoolean(false); protected final Object createdLock = new Object(); diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index 4ecfc9a49..853e02933 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -166,7 +166,7 @@ public class LwjglDisplay extends LwjglAbstractDisplay { return; } - new Thread(this, "LWJGL Renderer Thread").start(); + new Thread(this, THREAD_NAME).start(); if (waitFor) waitFor(true); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index efdb5dfdf..b06db1b29 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -170,7 +170,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { return; } - new Thread(this, "LWJGL Renderer Thread").start(); + new Thread(this, THREAD_NAME).start(); if (waitFor) waitFor(true); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index f52eaee01..8ad2425ad 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -137,7 +137,7 @@ public class TerrainLodControl extends AbstractControl { return Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { Thread th = new Thread(r); - th.setName("jME Terrain Thread"); + th.setName("jME3 Terrain Thread"); th.setDaemon(true); return th; } From 305e56a921925974e460332e257433278a813a93 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:47:21 -0400 Subject: [PATCH 027/225] ShaderNode: remove incorrect attribution --- .../src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java b/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java index db25a1217..9713d6ab8 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java @@ -39,8 +39,6 @@ import java.util.List; * Used for loading {@link ShaderNodeDefinition shader nodes definition} * * Tells if the defintion has to be loaded with or without its documentation - * - * @author Kirill Vainer */ public class ShaderNodeDefinitionKey extends AssetKey> { From 06408410cf70c242501d77d2ce7bc21cb788391d Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:47:54 -0400 Subject: [PATCH 028/225] CollisionResult: add hashCode() - required if we have equals() --- .../src/main/java/com/jme3/collision/CollisionResult.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java b/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java index a777bc9b6..ae271c880 100644 --- a/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java +++ b/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java @@ -109,6 +109,11 @@ public class CollisionResult implements Comparable { return super.equals(obj); } + @Override + public int hashCode() { + return Float.floatToIntBits(distance); + } + public Vector3f getContactPoint() { return contactPoint; } From a3467def1eaf14292c0ff7d49648c1386335ec22 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:48:24 -0400 Subject: [PATCH 029/225] WeakRefCloneAssetCache: remove useless "synchronized" (the map is already thread-safe) --- .../java/com/jme3/asset/cache/WeakRefCloneAssetCache.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java index e3566d22c..ab3581013 100644 --- a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java +++ b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java @@ -155,11 +155,7 @@ public class WeakRefCloneAssetCache implements AssetCache { } public T getFromCache(AssetKey key) { - AssetRef smartInfo; - synchronized (smartCache){ - smartInfo = smartCache.get(key); - } - + AssetRef smartInfo = smartCache.get(key); if (smartInfo == null) { return null; } else { From 26f702cc91ae0aa40be46b537f49931d661fa3f7 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:49:33 -0400 Subject: [PATCH 030/225] GLTracer: add more no-enum methods --- .../src/main/java/com/jme3/renderer/opengl/GLTracer.java | 4 ++++ 1 file changed, 4 insertions(+) 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 459e2d3a3..b69d524b4 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 @@ -70,6 +70,7 @@ public final class GLTracer implements InvocationHandler { // noEnumArgs("glTexParameteri", 2); noEnumArgs("glTexImage2D", 1, 3, 4, 5); noEnumArgs("glTexImage3D", 1, 3, 4, 5, 6); + noEnumArgs("glTexSubImage2D", 1, 2, 3, 4, 5); noEnumArgs("glTexSubImage3D", 1, 2, 3, 4, 5, 6, 7); noEnumArgs("glCompressedTexImage2D", 1, 3, 4, 5); noEnumArgs("glCompressedTexSubImage3D", 1, 2, 3, 4, 5, 6, 7); @@ -83,6 +84,8 @@ public final class GLTracer implements InvocationHandler { noEnumArgs("glDrawRangeElements", 1, 2, 3, 5); noEnumArgs("glDrawArrays", 1, 2); noEnumArgs("glDeleteBuffers", 0); + noEnumArgs("glBindVertexArray", 0); + noEnumArgs("glGenVertexArrays", 0); noEnumArgs("glBindFramebufferEXT", 1); noEnumArgs("glBindRenderbufferEXT", 1); @@ -110,6 +113,7 @@ public final class GLTracer implements InvocationHandler { noEnumArgs("glUniform1f", 0); noEnumArgs("glUniform2f", 0); noEnumArgs("glUniform3f", 0); + noEnumArgs("glUniform4", 0); noEnumArgs("glUniform4f", 0); noEnumArgs("glGetAttribLocation", 0, -1); noEnumArgs("glDetachShader", 0, 1); From fa324cad8ff71a2d77ec25b55dbe44b7f5faf6d1 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:49:59 -0400 Subject: [PATCH 031/225] Dome: fix crash when center = null --- jme3-core/src/main/java/com/jme3/scene/shape/Dome.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java b/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java index 3e2cfb031..8060498f9 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java @@ -266,7 +266,7 @@ public class Dome extends Mesh { vars.release(); // pole - vb.put(center.x).put(center.y + radius).put(center.z); + vb.put(this.center.x).put(this.center.y + radius).put(this.center.z); nb.put(0).put(insideView ? -1 : 1).put(0); tb.put(0.5f).put(1.0f); From 6252258c987dbd1afd9bcf6cf53fdb41dd2bfb7b Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 21:55:32 -0400 Subject: [PATCH 032/225] TechniqueDef: remove useless usesShaders variable --- .../src/main/java/com/jme3/material/TechniqueDef.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java index e618a046c..665742db3 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -98,7 +98,6 @@ public class TechniqueDef implements Savable { private EnumMap shaderName; private DefineList presetDefines; - private boolean usesShaders; private boolean usesNodes = false; private List shaderNodes; private ShaderGenerationInfo shaderGenerationInfo; @@ -207,7 +206,7 @@ public class TechniqueDef implements Savable { */ @Deprecated public boolean isUsingShaders(){ - return usesShaders; + return true; } /** @@ -248,7 +247,6 @@ public class TechniqueDef implements Savable { Caps fragCap = Caps.valueOf(fragLanguage); requiredCaps.add(fragCap); - usesShaders = true; } @@ -268,7 +266,6 @@ public class TechniqueDef implements Savable { requiredCaps.add(Caps.TesselationShader); } } - usesShaders=true; } /** @@ -449,7 +446,6 @@ public class TechniqueDef implements Savable { oc.write(lightMode, "lightMode", LightMode.Disable); oc.write(shadowMode, "shadowMode", ShadowMode.Disable); oc.write(renderState, "renderState", null); - oc.write(usesShaders, "usesShaders", false); oc.write(usesNodes, "usesNodes", false); oc.writeSavableArrayList((ArrayList)shaderNodes,"shaderNodes", null); oc.write(shaderGenerationInfo, "shaderGenerationInfo", null); @@ -472,7 +468,6 @@ public class TechniqueDef implements Savable { lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable); shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable); renderState = (RenderState) ic.readSavable("renderState", null); - usesShaders = ic.readBoolean("usesShaders", false); if (ic.getSavableVersion(TechniqueDef.class) == 0) { // Old version @@ -499,7 +494,6 @@ public class TechniqueDef implements Savable { public void setShaderNodes(List shaderNodes) { this.shaderNodes = shaderNodes; usesNodes = true; - usesShaders = true; } /** @@ -529,6 +523,6 @@ public class TechniqueDef implements Savable { //todo: make toString return something usefull @Override public String toString() { - return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesShaders=" + usesShaders + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}'; + return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}'; } } From 26109fcbacfaa976cd131d0499553ee390da3552 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 22:19:02 -0400 Subject: [PATCH 033/225] (SP)Lighting.vert: clarify comment regarding material colors --- .../src/main/resources/Common/MatDefs/Light/Lighting.vert | 3 ++- .../src/main/resources/Common/MatDefs/Light/SPLighting.vert | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert index 3ee6f38c6..f92b91fff 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -126,7 +126,8 @@ void main(){ DiffuseSum = m_Diffuse * vec4(lightColor.rgb, 1.0); SpecularSum = (m_Specular * lightColor).rgb; #else - AmbientSum = g_AmbientLightColor.rgb; // Default: ambient color is dark gray + // Defaults: Ambient and diffuse are white, specular is black. + AmbientSum = g_AmbientLightColor.rgb; DiffuseSum = vec4(lightColor.rgb, 1.0); SpecularSum = vec3(0.0); #endif 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 b0d8828a5..62b206b7e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -117,6 +117,7 @@ void main(){ SpecularSum = m_Specular.rgb; DiffuseSum = m_Diffuse; #else + // Defaults: Ambient and diffuse are white, specular is black. AmbientSum = g_AmbientLightColor.rgb; SpecularSum = vec3(0.0); DiffuseSum = vec4(1.0); From 22957c3d28a00a20533f1b45d271b4229f6f52cb Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 22:20:38 -0400 Subject: [PATCH 034/225] IOS Renderer: remove clear texture units (does nothing anyway) --- .../renderer/ios/IGLESShaderRenderer.java | 27 ------------------- 1 file changed, 27 deletions(-) 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 index d6b7010f4..8a59d0be8 100644 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java @@ -759,14 +759,6 @@ public class IGLESShaderRenderer implements Renderer { Image[] textures = context.boundTextures; int type = convertTextureType(tex.getType()); - if (!context.textureIndexList.moveToNew(unit)) { -// if (context.boundTextureUnit != unit){ -// glActiveTexture(GL_TEXTURE0 + unit); -// context.boundTextureUnit = unit; -// } -// glEnable(type); - } - if (textures[unit] != image) { if (context.boundTextureUnit != unit) { JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0 + unit); @@ -1768,7 +1760,6 @@ public class IGLESShaderRenderer implements Renderer { JmeIosGLES.checkGLError(); } clearVertexAttribs(); - clearTextureUnits(); } private void renderMeshDefault(Mesh mesh, int lod, int count) { @@ -1807,7 +1798,6 @@ public class IGLESShaderRenderer implements Renderer { JmeIosGLES.checkGLError(); } clearVertexAttribs(); - clearTextureUnits(); } @@ -2085,23 +2075,6 @@ public class IGLESShaderRenderer implements Renderer { context.attribIndexList.copyNewToOld(); } - - public void clearTextureUnits() { - IDList textureList = context.textureIndexList; - Image[] textures = context.boundTextures; - for (int i = 0; i < textureList.oldLen; i++) { - int idx = textureList.oldList[i]; -// if (context.boundTextureUnit != idx){ -// glActiveTexture(GL_TEXTURE0 + idx); -// context.boundTextureUnit = idx; -// } -// glDisable(convertTextureType(textures[idx].getType())); - textures[idx] = null; - } - context.textureIndexList.copyNewToOld(); - } - - public void updateFrameBuffer(FrameBuffer fb) { int id = fb.getId(); if (id == -1) { From 51952de16aec3afb4a3df793e469991142de05c1 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 22:21:14 -0400 Subject: [PATCH 035/225] jme3-testdata: fix issue #241 --- jme3-testdata/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jme3-testdata/build.gradle b/jme3-testdata/build.gradle index 1df94c4d2..aad9f2ae9 100644 --- a/jme3-testdata/build.gradle +++ b/jme3-testdata/build.gradle @@ -2,5 +2,12 @@ if (!hasProperty('mainClass')) { ext.mainClass = '' } +repositories { + maven { + url 'http://nifty-gui.sourceforge.net/nifty-maven-repo' + } +} + dependencies { + compile 'lessvoid:nifty-examples:1.4.1' } From d2af0017b227f2f2087a71f2e4fa85a74866b950 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 22:40:18 -0400 Subject: [PATCH 036/225] Nifty BatchRenderBackend: set colorspace property on images --- .../main/java/com/jme3/niftygui/JmeBatchRenderBackend.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java index 97f46120a..9dd831020 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java @@ -55,6 +55,7 @@ import com.jme3.texture.Image.Format; import com.jme3.texture.Texture.MagFilter; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; import de.lessvoid.nifty.render.batch.spi.BatchRenderBackend; @@ -302,7 +303,7 @@ public class JmeBatchRenderBackend implements BatchRenderBackend { initialData.rewind(); modifyTexture( getTextureAtlas(atlasTextureId), - new com.jme3.texture.Image(Format.RGBA8, image.getWidth(), image.getHeight(), initialData), + new com.jme3.texture.Image(Format.RGBA8, image.getWidth(), image.getHeight(), initialData, ColorSpace.sRGB), x, y); } @@ -338,7 +339,7 @@ public class JmeBatchRenderBackend implements BatchRenderBackend { } initialData.rewind(); - Texture2D texture = new Texture2D(new com.jme3.texture.Image(Format.RGBA8, width, height, initialData)); + Texture2D texture = new Texture2D(new com.jme3.texture.Image(Format.RGBA8, width, height, initialData, ColorSpace.sRGB)); texture.setMinFilter(MinFilter.NearestNoMipMaps); texture.setMagFilter(MagFilter.Nearest); return texture; From 19338deaf88ae1683df2b83fefff77f1d30500ca Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 22:42:34 -0400 Subject: [PATCH 037/225] LWJGL Backend: fix issue #232 --- .../main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java index ee6a81c53..7cdca0a57 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -181,7 +181,9 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna // check input after we synchronize with framerate. // this reduces input lag. - Display.processMessages(); + if (renderable.get()){ + Display.processMessages(); + } // Subclasses just call GLObjectManager clean up objects here // it is safe .. for now. From 414e1b3fffcb770d894564cfc523899c4d6548cc Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 23:34:53 -0400 Subject: [PATCH 038/225] Lighting.glsllib: Use quadratic spotlight falloff in SRGB mode --- .../main/resources/Common/ShaderLib/Lighting.glsllib | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib index 6ac1fce5c..fb8f40524 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib @@ -30,6 +30,14 @@ float computeSpotFalloff(in vec4 lightDirection, in vec3 lightVector){ float innerAngleCos = floor(lightDirection.w) * 0.001; float outerAngleCos = fract(lightDirection.w); float innerMinusOuter = innerAngleCos - outerAngleCos; - return clamp((curAngleCos - outerAngleCos) / innerMinusOuter, step(lightDirection.w, 0.001), 1.0); + float falloff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, step(lightDirection.w, 0.001), 1.0); + +#ifdef SRGB + // Use quadratic falloff (notice the ^4) + return pow(clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0), 4.0); +#else + // Use linear falloff + return falloff; +#endif } From 38e4580857e032137741278563645cbf14ff6903 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 27 Mar 2015 23:37:13 -0400 Subject: [PATCH 039/225] TestSweepTest: fix crash on native bullet (natives not loaded when shapes are created) --- .../src/main/java/jme3test/bullet/TestSweepTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java index 6f9e3fdd6..c8bd631d9 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java @@ -21,8 +21,8 @@ import java.util.List; public class TestSweepTest extends SimpleApplication { private BulletAppState bulletAppState = new BulletAppState(); - private CapsuleCollisionShape obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f); - private CapsuleCollisionShape capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f); + private CapsuleCollisionShape obstacleCollisionShape; + private CapsuleCollisionShape capsuleCollisionShape; private Node capsule; private Node obstacle; private float dist = .5f; @@ -33,6 +33,9 @@ public class TestSweepTest extends SimpleApplication { @Override public void simpleInitApp() { + obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f); + capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f); + stateManager.attach(bulletAppState); capsule = new Node("capsule"); From 5c8b1fe585282034966b1e31920c58e251ddfdf3 Mon Sep 17 00:00:00 2001 From: rainmantsr Date: Sat, 28 Mar 2015 13:30:53 +0100 Subject: [PATCH 040/225] Corrected JavaDoc comment --- jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 5aa4f6abf..380dc3e16 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -845,7 +845,7 @@ public class PhysicsSpace { /** * Performs a sweep collision test and returns the results as a list of * PhysicsSweepTestResults
You have to use different Transforms for - * start and end (at least distance > 0.4f). SweepTest will not see a + * start and end (at least distance > allowedCcdPenetration). SweepTest will not see a * collision if it starts INSIDE an object and is moving AWAY from its * center. */ From fe7f30d5ec0628f8ab62a343a97007239c2a4243 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Mar 2015 13:17:16 -0400 Subject: [PATCH 041/225] Native SweepTest: fix formatting --- .../cpp/com_jme3_bullet_PhysicsSpace.cpp | 117 +++++++++--------- .../src/native/cpp/jmeBulletUtil.h | 6 +- 2 files changed, 61 insertions(+), 62 deletions(-) diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp index 32bc249dd..f9be6a6ad 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp @@ -470,65 +470,64 @@ extern "C" { - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native - (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) { - - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - - btCollisionShape* shape = reinterpret_cast (shapeId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The shape does not exist."); - return; - } - - struct AllConvexResultCallback : public btCollisionWorld::ConvexResultCallback { - - AllConvexResultCallback(const btTransform& convexFromWorld, const btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) { - } - jobject resultlist; - JNIEnv* env; - btTransform m_convexFromWorld; //used to calculate hitPointWorld from hitFraction - btTransform m_convexToWorld; - - btVector3 m_hitNormalWorld; - btVector3 m_hitPointWorld; - - virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { - if (normalInWorldSpace) { - m_hitNormalWorld = convexResult.m_hitNormalLocal; - } - else { - m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal; - } - m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction); - - jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject); - - return 1.f; - } - }; - - btTransform native_to = btTransform(); - jmeBulletUtil::convert(env, to, &native_to); - - btTransform native_from = btTransform(); - jmeBulletUtil::convert(env, from, &native_from); - - btScalar native_allowed_ccd_penetration = btScalar(allowedCcdPenetration); - - - AllConvexResultCallback resultCallback(native_from, native_to); - resultCallback.env = env; - resultCallback.resultlist = resultlist; - space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration); - return; - } + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native + (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) { + + jmePhysicsSpace* space = reinterpret_cast (spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + + btCollisionShape* shape = reinterpret_cast (shapeId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The shape does not exist."); + return; + } + + struct AllConvexResultCallback : public btCollisionWorld::ConvexResultCallback { + + AllConvexResultCallback(const btTransform& convexFromWorld, const btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) { + } + jobject resultlist; + JNIEnv* env; + btTransform m_convexFromWorld; //used to calculate hitPointWorld from hitFraction + btTransform m_convexToWorld; + + btVector3 m_hitNormalWorld; + btVector3 m_hitPointWorld; + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { + if (normalInWorldSpace) { + m_hitNormalWorld = convexResult.m_hitNormalLocal; + } + else { + m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal; + } + m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction); + + jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject); + + return 1.f; + } + }; + + btTransform native_to = btTransform(); + jmeBulletUtil::convert(env, to, &native_to); + + btTransform native_from = btTransform(); + jmeBulletUtil::convert(env, from, &native_from); + + btScalar native_allowed_ccd_penetration = btScalar(allowedCcdPenetration); + + AllConvexResultCallback resultCallback(native_from, native_to); + resultCallback.env = env; + resultCallback.resultlist = resultlist; + space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration); + return; + } #ifdef __cplusplus } diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h index fa09d09cc..7d982c80e 100644 --- a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h +++ b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h @@ -42,13 +42,13 @@ public: static void convert(JNIEnv* env, jobject in, btVector3* out); static void convert(JNIEnv* env, const btVector3* in, jobject out); static void convert(JNIEnv* env, jobject in, btMatrix3x3* out); - static void convert(JNIEnv* env, jobject in, btQuaternion* out); + static void convert(JNIEnv* env, jobject in, btQuaternion* out); static void convert(JNIEnv* env, const btMatrix3x3* in, jobject out); static void convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out); static void convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out); - static void convert(JNIEnv* env, jobject in, btTransform* out); + static void convert(JNIEnv* env, jobject in, btTransform* out); static void addResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld,const btScalar m_hitFraction,const btCollisionObject* hitobject); - static void addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, const btScalar m_hitFraction, const btCollisionObject* hitobject); + static void addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, const btScalar m_hitFraction, const btCollisionObject* hitobject); private: jmeBulletUtil(){}; ~jmeBulletUtil(){}; From 87b6c117f25a53a4d072faac0beb6cf32d5e529a Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Mar 2015 13:23:00 -0400 Subject: [PATCH 042/225] TestSweepTest: ignore collisions against ourselves (needed for native bullet) --- .../main/java/jme3test/bullet/TestSweepTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java index c8bd631d9..0613e5d22 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java @@ -59,14 +59,19 @@ public class TestSweepTest extends SimpleApplication { public void simpleUpdate(float tpf) { float move = tpf * 1; + boolean colliding = false; List sweepTest = bulletAppState.getPhysicsSpace().sweepTest(capsuleCollisionShape, new Transform(capsule.getWorldTranslation()), new Transform(capsule.getWorldTranslation().add(dist, 0, 0))); - if (sweepTest.size() > 0) { - PhysicsSweepTestResult get = sweepTest.get(0); - PhysicsCollisionObject collisionObject = get.getCollisionObject(); - fpsText.setText("Almost colliding with " + collisionObject.getUserObject().toString()); - } else { + for (PhysicsSweepTestResult result : sweepTest) { + if (result.getCollisionObject().getCollisionShape() != capsuleCollisionShape) { + PhysicsCollisionObject collisionObject = result.getCollisionObject(); + fpsText.setText("Almost colliding with " + collisionObject.getUserObject().toString()); + colliding = true; + } + } + + if (!colliding) { // if the sweep is clear then move the spatial capsule.move(move, 0, 0); } From 36493353c9f1fef5559174bf20f5ddc9ebc62613 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sun, 29 Mar 2015 11:47:58 -0400 Subject: [PATCH 043/225] TechniqueDef: fix caps loading for shader language versions --- .../java/com/jme3/material/TechniqueDef.java | 112 +++++++++--------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java index 665742db3..f8152e563 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -94,8 +94,8 @@ public class TechniqueDef implements Savable { private EnumSet requiredCaps = EnumSet.noneOf(Caps.class); private String name; - private EnumMap shaderLanguage; - private EnumMap shaderName; + private EnumMap shaderLanguages; + private EnumMap shaderNames; private DefineList presetDefines; private boolean usesNodes = false; @@ -128,8 +128,8 @@ public class TechniqueDef implements Savable { * Serialization only. Do not use. */ public TechniqueDef(){ - shaderLanguage=new EnumMap(Shader.ShaderType.class); - shaderName=new EnumMap(Shader.ShaderType.class); + shaderLanguages=new EnumMap(Shader.ShaderType.class); + shaderNames=new EnumMap(Shader.ShaderType.class); } /** @@ -238,31 +238,41 @@ public class TechniqueDef implements Savable { * @param fragLanguage The fragment shader language */ public void setShaderFile(String vertexShader, String fragmentShader, String vertLanguage, String fragLanguage) { - this.shaderLanguage.put(Shader.ShaderType.Vertex, vertLanguage); - this.shaderName.put(Shader.ShaderType.Vertex, vertexShader); - this.shaderLanguage.put(Shader.ShaderType.Fragment, fragLanguage); - this.shaderName.put(Shader.ShaderType.Fragment, fragmentShader); + this.shaderLanguages.put(Shader.ShaderType.Vertex, vertLanguage); + this.shaderNames.put(Shader.ShaderType.Vertex, vertexShader); + this.shaderLanguages.put(Shader.ShaderType.Fragment, fragLanguage); + this.shaderNames.put(Shader.ShaderType.Fragment, fragmentShader); + + requiredCaps.clear(); Caps vertCap = Caps.valueOf(vertLanguage); requiredCaps.add(vertCap); Caps fragCap = Caps.valueOf(fragLanguage); requiredCaps.add(fragCap); - } /** * Sets the shaders that this technique definition will use. * - * @param shaderName EnumMap containing all shader names for this stage - * @param shaderLanguage EnumMap containing all shader languages for this stage + * @param shaderNames EnumMap containing all shader names for this stage + * @param shaderLanguages EnumMap containing all shader languages for this stage */ - public void setShaderFile(EnumMap shaderName, EnumMap shaderLanguage) { - for (Shader.ShaderType shaderType : shaderName.keySet()) { - this.shaderLanguage.put(shaderType,shaderLanguage.get(shaderType)); - this.shaderName.put(shaderType,shaderName.get(shaderType)); - if(shaderType.equals(Shader.ShaderType.Geometry)){ + public void setShaderFile(EnumMap shaderNames, EnumMap shaderLanguages) { + requiredCaps.clear(); + + for (Shader.ShaderType shaderType : shaderNames.keySet()) { + String language = shaderLanguages.get(shaderType); + String shaderFile = shaderNames.get(shaderType); + + this.shaderLanguages.put(shaderType, language); + this.shaderNames.put(shaderType, shaderFile); + + Caps vertCap = Caps.valueOf(language); + requiredCaps.add(vertCap); + + if (shaderType.equals(Shader.ShaderType.Geometry)) { requiredCaps.add(Caps.GeometryShader); - }else if(shaderType.equals(Shader.ShaderType.TessellationControl)){ + } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) { requiredCaps.add(Caps.TesselationShader); } } @@ -340,7 +350,7 @@ public class TechniqueDef implements Savable { * @return the name of the fragment shader to be used. */ public String getFragmentShaderName() { - return shaderName.get(Shader.ShaderType.Fragment); + return shaderNames.get(Shader.ShaderType.Fragment); } @@ -351,42 +361,34 @@ public class TechniqueDef implements Savable { * @return the name of the vertex shader to be used. */ public String getVertexShaderName() { - return shaderName.get(Shader.ShaderType.Vertex); - } - - /** - * @deprecated Use {@link #getVertexShaderLanguage() } instead. - */ - @Deprecated - public String getShaderLanguage() { - return shaderLanguage.get(Shader.ShaderType.Vertex); + return shaderNames.get(Shader.ShaderType.Vertex); } /** * Returns the language of the fragment shader used in this technique. */ public String getFragmentShaderLanguage() { - return shaderLanguage.get(Shader.ShaderType.Fragment); + return shaderLanguages.get(Shader.ShaderType.Fragment); } /** * Returns the language of the vertex shader used in this technique. */ public String getVertexShaderLanguage() { - return shaderLanguage.get(Shader.ShaderType.Vertex); + return shaderLanguages.get(Shader.ShaderType.Vertex); } /**Returns the language for each shader program * @param shaderType */ public String getShaderProgramLanguage(Shader.ShaderType shaderType){ - return shaderLanguage.get(shaderType); + return shaderLanguages.get(shaderType); } /**Returns the name for each shader program * @param shaderType */ public String getShaderProgramName(Shader.ShaderType shaderType){ - return shaderName.get(shaderType); + return shaderNames.get(shaderType); } /** @@ -431,16 +433,16 @@ public class TechniqueDef implements Savable { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); - oc.write(shaderName.get(Shader.ShaderType.Vertex), "vertName", null); - oc.write(shaderName.get(Shader.ShaderType.Fragment), "fragName", null); - oc.write(shaderName.get(Shader.ShaderType.Geometry), "geomName", null); - oc.write(shaderName.get(Shader.ShaderType.TessellationControl), "tsctrlName", null); - oc.write(shaderName.get(Shader.ShaderType.TessellationEvaluation), "tsevalName", null); - oc.write(shaderLanguage.get(Shader.ShaderType.Vertex), "vertLanguage", null); - oc.write(shaderLanguage.get(Shader.ShaderType.Fragment), "fragLanguage", null); - oc.write(shaderLanguage.get(Shader.ShaderType.Geometry), "geomLanguage", null); - oc.write(shaderLanguage.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null); - oc.write(shaderLanguage.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null); + oc.write(shaderNames.get(Shader.ShaderType.Vertex), "vertName", null); + oc.write(shaderNames.get(Shader.ShaderType.Fragment), "fragName", null); + oc.write(shaderNames.get(Shader.ShaderType.Geometry), "geomName", null); + oc.write(shaderNames.get(Shader.ShaderType.TessellationControl), "tsctrlName", null); + oc.write(shaderNames.get(Shader.ShaderType.TessellationEvaluation), "tsevalName", null); + oc.write(shaderLanguages.get(Shader.ShaderType.Vertex), "vertLanguage", null); + oc.write(shaderLanguages.get(Shader.ShaderType.Fragment), "fragLanguage", null); + oc.write(shaderLanguages.get(Shader.ShaderType.Geometry), "geomLanguage", null); + oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null); + oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null); oc.write(presetDefines, "presetDefines", null); oc.write(lightMode, "lightMode", LightMode.Disable); @@ -459,11 +461,11 @@ public class TechniqueDef implements Savable { public void read(JmeImporter im) throws IOException{ InputCapsule ic = im.getCapsule(this); name = ic.readString("name", null); - shaderName.put(Shader.ShaderType.Vertex,ic.readString("vertName", null)); - shaderName.put(Shader.ShaderType.Fragment,ic.readString("fragName", null)); - shaderName.put(Shader.ShaderType.Geometry,ic.readString("geomName", null)); - shaderName.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null)); - shaderName.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null)); + shaderNames.put(Shader.ShaderType.Vertex,ic.readString("vertName", null)); + shaderNames.put(Shader.ShaderType.Fragment,ic.readString("fragName", null)); + shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null)); + shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null)); + shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null)); presetDefines = (DefineList) ic.readSavable("presetDefines", null); lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable); shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable); @@ -471,15 +473,15 @@ public class TechniqueDef implements Savable { if (ic.getSavableVersion(TechniqueDef.class) == 0) { // Old version - shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("shaderLang", null)); - shaderLanguage.put(Shader.ShaderType.Fragment,shaderLanguage.get(Shader.ShaderType.Vertex)); + shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("shaderLang", null)); + shaderLanguages.put(Shader.ShaderType.Fragment,shaderLanguages.get(Shader.ShaderType.Vertex)); } else { // New version - shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("vertLanguage", null)); - shaderLanguage.put(Shader.ShaderType.Fragment,ic.readString("fragLanguage", null)); - shaderLanguage.put(Shader.ShaderType.Geometry,ic.readString("geomLanguage", null)); - shaderLanguage.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlLanguage", null)); - shaderLanguage.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalLanguage", null)); + shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("vertLanguage", null)); + shaderLanguages.put(Shader.ShaderType.Fragment,ic.readString("fragLanguage", null)); + shaderLanguages.put(Shader.ShaderType.Geometry,ic.readString("geomLanguage", null)); + shaderLanguages.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlLanguage", null)); + shaderLanguages.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalLanguage", null)); } usesNodes = ic.readBoolean("usesNodes", false); @@ -501,7 +503,7 @@ public class TechniqueDef implements Savable { * @return */ public EnumMap getShaderProgramNames() { - return shaderName; + return shaderNames; } /** @@ -509,7 +511,7 @@ public class TechniqueDef implements Savable { * @return */ public EnumMap getShaderProgramLanguages() { - return shaderLanguage; + return shaderLanguages; } public ShaderGenerationInfo getShaderGenerationInfo() { From ca76754889244eed407860b9cc7c953822b22004 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Sun, 29 Mar 2015 23:02:05 -0400 Subject: [PATCH 044/225] SDK: Add missing global library for android native bullet --- sdk/build.gradle | 31 ++++++++++--------- .../baselibs/jme3-bullet-native-android.xml | 19 ++++++++++++ .../com/jme3/gde/project/baselibs/layer.xml | 1 + 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-bullet-native-android.xml diff --git a/sdk/build.gradle b/sdk/build.gradle index 55722cbfb..745b878c6 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -25,17 +25,18 @@ dependencies { corelibs project(':jme3-niftygui') corelibs project(':jme3-plugins') corelibs project(':jme3-terrain') - + optlibs project(':jme3-bullet') optlibs project(':jme3-jogl') optlibs project(':jme3-android') optlibs project(':jme3-ios') optlibs project(':jme3-android-native') optlibs project(':jme3-bullet-native') + optlibs project(':jme3-bullet-native-android') testdatalibs project(':jme3-testdata') examplelibs project(':jme3-examples') - + } artifacts { @@ -97,7 +98,7 @@ task createBaseXml(dependsOn: configurations.corelibs) <<{ "jme3-core-baselibs and jme3-core-libraries" def jmeJarFiles = [] // jme3 jar files def externalJarFiles = [] // external jar files - + // collect jar files project.configurations.corelibs.dependencies.each {dep -> // collect external jar files @@ -120,7 +121,7 @@ task createBaseXml(dependsOn: configurations.corelibs) <<{ def packages = [] jmeJarFiles.each{jarFile -> ZipFile file = new ZipFile(jarFile) - file.entries().each { entry -> + file.entries().each { entry -> if(entry.name.endsWith('.class')){ // TODO: "/" works on windows? def pathPart = entry.name.substring(0,entry.name.lastIndexOf('/')) @@ -129,14 +130,14 @@ task createBaseXml(dependsOn: configurations.corelibs) <<{ packages.add(classPath) } } - } + } } - + // collect library packages def extPackages = [] externalJarFiles.each{jarFile -> ZipFile file = new ZipFile(jarFile) - file.entries().each { entry -> + file.entries().each { entry -> if(entry.name.endsWith('.class')){ // TODO: "/" works on windows? def pathPart = entry.name.substring(0,entry.name.lastIndexOf('/')) @@ -145,9 +146,9 @@ task createBaseXml(dependsOn: configurations.corelibs) <<{ extPackages.add(classPath) } } - } + } } - + def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.mkp.xmlDeclaration(version:'1.0') @@ -252,7 +253,7 @@ task copyProjectLibs(dependsOn: [configurations.corelibs, configurations.testdat into "jme3-project-libraries/release/libs/" } } - + project.configurations.testdatalibs.dependencies.each {dep -> // copy jme3 test data to jme3-project-testdata dep.dependencyProject.configurations.archives.allArtifacts.each{ artifact-> @@ -281,10 +282,10 @@ def makeFile(builder, nameR) { builder.file(name:nameR, url:nameR) } task createProjectXml(dependsOn: configurations.corelibs) <<{ description "Creates needed J2SE library and layer XML files in jme3-project-baselibs" - - def eol = System.properties.'line.separator' + + def eol = System.properties.'line.separator' def j2seLibraries = [] // created J2SE library descriptors - + // for each dependency in corelibs.. def deps = [] deps.addAll(project.configurations.corelibs.dependencies) @@ -315,7 +316,7 @@ task createProjectXml(dependsOn: configurations.corelibs) <<{ def libraryWriter = new StringWriter() def libraryXml = new MarkupBuilder(libraryWriter) // xml.mkp.xmlDeclaration(version:'1.0') - libraryWriter << '' << eol + libraryWriter << '' << eol libraryWriter << '' << eol libraryXml.library(version:"1.0", encoding: "UTF-8"){ makeName(libraryXml, "${dep.dependencyProject.name}") @@ -352,7 +353,7 @@ task createProjectXml(dependsOn: configurations.corelibs) <<{ def layerWriter = new StringWriter() def layerXml = new MarkupBuilder(layerWriter) // layerXml.mkp.xmlDeclaration(version:'1.0') - layerWriter << '' << eol + layerWriter << '' << eol layerWriter << '' << eol layerXml.filesystem{ folder(name:"org-netbeans-api-project-libraries"){ diff --git a/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-bullet-native-android.xml b/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-bullet-native-android.xml new file mode 100644 index 000000000..083970e02 --- /dev/null +++ b/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-bullet-native-android.xml @@ -0,0 +1,19 @@ + + + + jme3-bullet-native-android + j2se + com.jme3.gde.project.baselibs.Bundle + + classpath + jar:nbinst://com.jme3.gde.project.baselibs/libs/jme3-bullet-native-android-3.1.0-snapshot-github.jar!/ + + + src + jar:nbinst://com.jme3.gde.project.baselibs/libs/jme3-bullet-native-android-3.1.0-snapshot-github-sources.jar!/ + + + javadoc + jar:nbinst://com.jme3.gde.project.baselibs/libs/jme3-bullet-native-android-3.1.0-snapshot-github-javadoc.jar!/ + + \ No newline at end of file diff --git a/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/layer.xml b/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/layer.xml index 1a7e7bf0e..837ff7e16 100644 --- a/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/layer.xml +++ b/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/layer.xml @@ -20,6 +20,7 @@ + \ No newline at end of file From 1fa4eb441d392640e0f672c5d17435002fa83744 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Tue, 31 Mar 2015 13:12:15 -0400 Subject: [PATCH 045/225] SDK: Remove android.jar from jme3-android global library --- .../src/com/jme3/gde/project/baselibs/jme3-android.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-android.xml b/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-android.xml index 801e5bb3b..d937b98a9 100644 --- a/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-android.xml +++ b/sdk/jme3-project-baselibs/src/com/jme3/gde/project/baselibs/jme3-android.xml @@ -7,7 +7,6 @@ classpath jar:nbinst://com.jme3.gde.project.baselibs/libs/jme3-android-3.1.0-snapshot-github.jar!/ - jar:nbinst://com.jme3.gde.project.libraries/libs/android.jar!/ src From 32275596bf279c06333bfe9deda9e1779d598d87 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Tue, 31 Mar 2015 13:29:25 -0400 Subject: [PATCH 046/225] SDK: Update mobile-impl.xml in user projects for jME 3.1 libraries --- .../com/jme3/gde/android/mobile-targets.xml | 78 +++++++------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml b/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml index 437500ba6..cadd23107 100644 --- a/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml +++ b/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml @@ -29,58 +29,56 @@ - + - - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + Adding libraries for android. - + + - - Replacing bullet library with android native version. - - - - - + + Replacing android native bullet library. + + + + + - - - - - - - @@ -91,25 +89,9 @@ - - Adding OpenAL Soft - - - - - - - - - - - - - - - + From 00a2a844470c51d829b0c87fb838a4c61917133e Mon Sep 17 00:00:00 2001 From: iwgeric Date: Tue, 31 Mar 2015 13:32:26 -0400 Subject: [PATCH 047/225] SDK: fix message during android build --- sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml b/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml index cadd23107..d57833296 100644 --- a/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml +++ b/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml @@ -71,7 +71,7 @@ - Replacing android native bullet library. + Replacing bullet library with android native bullet version. From 1569e9583a9a5c0c0ec317df8ac44424445f88a4 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 13:08:53 -0400 Subject: [PATCH 048/225] Android: remove deprecated screenOrientation from AndroidHarness. Screen orientation is set in manifest instead of in MainActivity. --- .../java/com/jme3/app/AndroidHarness.java | 1166 ++++++++--------- 1 file changed, 580 insertions(+), 586 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java index 1d2835bc3..b103e92df 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java @@ -1,586 +1,580 @@ -package com.jme3.app; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.pm.ActivityInfo; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; -import android.opengl.GLSurfaceView; -import android.os.Bundle; -import android.util.Log; -import android.view.*; -import android.view.ViewGroup.LayoutParams; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; -import com.jme3.audio.AudioRenderer; -import com.jme3.input.JoyInput; -import com.jme3.input.TouchInput; -import com.jme3.input.android.AndroidSensorJoyInput; -import com.jme3.input.controls.TouchListener; -import com.jme3.input.controls.TouchTrigger; -import com.jme3.input.event.TouchEvent; -import com.jme3.system.AppSettings; -import com.jme3.system.SystemListener; -import com.jme3.system.android.JmeAndroidSystem; -import com.jme3.system.android.OGLESContext; -import com.jme3.util.AndroidLogHandler; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; - -/** - * AndroidHarness wraps a jme application object and runs it on - * Android - * - * @author Kirill - * @author larynx - */ -public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener, SystemListener { - - protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName()); - /** - * The application class to start - */ - protected String appClass = "jme3test.android.Test"; - /** - * The jme3 application object - */ - protected Application app = null; - - /** - * Sets the desired RGB size for the surfaceview. 16 = RGB565, 24 = RGB888. - * (default = 24) - */ - protected int eglBitsPerPixel = 24; - - /** - * Sets the desired number of Alpha bits for the surfaceview. This affects - * how the surfaceview is able to display Android views that are located - * under the surfaceview jME uses to render the scenegraph. - * 0 = Opaque surfaceview background (fastest) - * 1->7 = Transparent surfaceview background - * 8 or higher = Translucent surfaceview background - * (default = 0) - */ - protected int eglAlphaBits = 0; - - /** - * The number of depth bits specifies the precision of the depth buffer. - * (default = 16) - */ - protected int eglDepthBits = 16; - - /** - * Sets the number of samples to use for multisampling.
- * Leave 0 (default) to disable multisampling.
- * Set to 2 or 4 to enable multisampling. - */ - protected int eglSamples = 0; - - /** - * Set the number of stencil bits. - * (default = 0) - */ - protected int eglStencilBits = 0; - - /** - * Set the desired frame rate. If frameRate > 0, the application - * will be capped at the desired frame rate. - * (default = -1, no frame rate cap) - */ - protected int frameRate = -1; - - /** - * Sets the type of Audio Renderer to be used. - *

- * Android MediaPlayer / SoundPool can be used on all - * supported Android platform versions (2.2+)
- * OpenAL Soft uses an OpenSL backend and is only supported on Android - * versions 2.3+. - *

- * Only use ANDROID_ static strings found in AppSettings - * - */ - protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; - - /** - * If true Android Sensors are used as simulated Joysticks. Users can use the - * Android sensor feedback through the RawInputListener or by registering - * JoyAxisTriggers. - */ - protected boolean joystickEventsEnabled = false; - /** - * If true KeyEvents are generated from TouchEvents - */ - protected boolean keyEventsEnabled = true; - /** - * If true MouseEvents are generated from TouchEvents - */ - protected boolean mouseEventsEnabled = true; - /** - * Flip X axis - */ - protected boolean mouseEventsInvertX = false; - /** - * Flip Y axis - */ - protected boolean mouseEventsInvertY = false; - /** - * if true finish this activity when the jme app is stopped - */ - protected boolean finishOnAppStop = true; - /** - * set to false if you don't want the harness to handle the exit hook - */ - protected boolean handleExitHook = true; - /** - * Title of the exit dialog, default is "Do you want to exit?" - */ - protected String exitDialogTitle = "Do you want to exit?"; - /** - * Message of the exit dialog, default is "Use your home key to bring this - * app into the background or exit to terminate it." - */ - protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; - /** - * Set the screen window mode. If screenFullSize is true, then the - * notification bar and title bar are removed and the screen covers the - * entire display. If screenFullSize is false, then the notification bar - * remains visible if screenShowTitle is true while screenFullScreen is - * false, then the title bar is also displayed under the notification bar. - */ - protected boolean screenFullScreen = true; - /** - * if screenShowTitle is true while screenFullScreen is false, then the - * title bar is also displayed under the notification bar - */ - protected boolean screenShowTitle = true; - /** - * Splash Screen picture Resource ID. If a Splash Screen is desired, set - * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If - * splashPicID = 0, then no splash screen will be displayed. - */ - protected int splashPicID = 0; - - /** - * No longer used - Use the android:screenOrientation declaration in - * the AndroidManifest.xml file. - */ - @Deprecated - protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR; - - protected OGLESContext ctx; - protected GLSurfaceView view = null; - protected boolean isGLThreadPaused = true; - protected ImageView splashImageView = null; - protected FrameLayout frameLayout = null; - final private String ESCAPE_EVENT = "TouchEscape"; - private boolean firstDrawFrame = true; - private boolean inConfigChange = false; - - private class DataObject { - protected Application app = null; - } - - @Override - public Object onRetainNonConfigurationInstance() { - logger.log(Level.FINE, "onRetainNonConfigurationInstance"); - final DataObject data = new DataObject(); - data.app = this.app; - inConfigChange = true; - return data; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - initializeLogHandler(); - - logger.fine("onCreate"); - super.onCreate(savedInstanceState); - - if (screenFullScreen) { - requestWindowFeature(Window.FEATURE_NO_TITLE); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - if (!screenShowTitle) { - requestWindowFeature(Window.FEATURE_NO_TITLE); - } - } - - final DataObject data = (DataObject) getLastNonConfigurationInstance(); - if (data != null) { - logger.log(Level.FINE, "Using Retained App"); - this.app = data.app; - } else { - // Discover the screen reolution - //TODO try to find a better way to get a hand on the resolution - WindowManager wind = this.getWindowManager(); - Display disp = wind.getDefaultDisplay(); - Log.d("AndroidHarness", "Resolution from Window, width:" + disp.getWidth() + ", height: " + disp.getHeight()); - - // Create Settings - logger.log(Level.FINE, "Creating settings"); - AppSettings settings = new AppSettings(true); - settings.setEmulateMouse(mouseEventsEnabled); - settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY); - settings.setUseJoysticks(joystickEventsEnabled); - settings.setEmulateKeyboard(keyEventsEnabled); - - settings.setBitsPerPixel(eglBitsPerPixel); - settings.setAlphaBits(eglAlphaBits); - settings.setDepthBits(eglDepthBits); - settings.setSamples(eglSamples); - settings.setStencilBits(eglStencilBits); - - settings.setResolution(disp.getWidth(), disp.getHeight()); - settings.setAudioRenderer(audioRendererType); - - settings.setFrameRate(frameRate); - - // Create application instance - try { - if (app == null) { - @SuppressWarnings("unchecked") - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); - } - - app.setSettings(settings); - app.start(); - } catch (Exception ex) { - handleError("Class " + appClass + " init failed", ex); - setContentView(new TextView(this)); - } - } - - ctx = (OGLESContext) app.getContext(); - view = ctx.createView(this); - // store the glSurfaceView in JmeAndroidSystem for future use - JmeAndroidSystem.setView(view); - // AndroidHarness wraps the app as a SystemListener. - ctx.setSystemListener(this); - layoutDisplay(); - } - - @Override - protected void onRestart() { - logger.fine("onRestart"); - super.onRestart(); - if (app != null) { - app.restart(); - } - } - - @Override - protected void onStart() { - logger.fine("onStart"); - super.onStart(); - } - - @Override - protected void onResume() { - logger.fine("onResume"); - super.onResume(); - - gainFocus(); - } - - @Override - protected void onPause() { - logger.fine("onPause"); - loseFocus(); - - super.onPause(); - } - - @Override - protected void onStop() { - logger.fine("onStop"); - super.onStop(); - } - - @Override - protected void onDestroy() { - logger.fine("onDestroy"); - final DataObject data = (DataObject) getLastNonConfigurationInstance(); - if (data != null || inConfigChange) { - logger.fine("In Config Change, not stopping app."); - } else { - if (app != null) { - app.stop(!isGLThreadPaused); - } - } - setContentView(new TextView(this)); - ctx = null; - app = null; - view = null; - JmeAndroidSystem.setView(null); - - super.onDestroy(); - } - - public Application getJmeApplication() { - return app; - } - - /** - * Called when an error has occurred. By default, will show an error message - * to the user and print the exception/error to the log. - */ - @Override - public void handleError(final String errorMsg, final Throwable t) { - String stackTrace = ""; - String title = "Error"; - - if (t != null) { - // Convert exception to string - StringWriter sw = new StringWriter(100); - t.printStackTrace(new PrintWriter(sw)); - stackTrace = sw.toString(); - title = t.toString(); - } - - final String finalTitle = title; - final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") - + "\n" + stackTrace; - - logger.log(Level.SEVERE, finalMsg); - - runOnUiThread(new Runnable() { - @Override - public void run() { - AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) - .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create(); - dialog.show(); - } - }); - } - - /** - * Called by the android alert dialog, terminate the activity and OpenGL - * rendering - * - * @param dialog - * @param whichButton - */ - public void onClick(DialogInterface dialog, int whichButton) { - if (whichButton != -2) { - if (app != null) { - app.stop(true); - } - app = null; - this.finish(); - } - } - - /** - * Gets called by the InputManager on all touch/drag/scale events - */ - @Override - public void onTouch(String name, TouchEvent evt, float tpf) { - if (name.equals(ESCAPE_EVENT)) { - switch (evt.getType()) { - case KEY_UP: - runOnUiThread(new Runnable() { - @Override - public void run() { - AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) - .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create(); - dialog.show(); - } - }); - break; - default: - break; - } - } - } - - public void layoutDisplay() { - logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); - if (view == null) { - logger.log(Level.FINE, "view is null!"); - } - if (splashPicID != 0) { - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT, - Gravity.CENTER); - - frameLayout = new FrameLayout(this); - splashImageView = new ImageView(this); - - Drawable drawable = this.getResources().getDrawable(splashPicID); - if (drawable instanceof NinePatchDrawable) { - splashImageView.setBackgroundDrawable(drawable); - } else { - splashImageView.setImageResource(splashPicID); - } - - if (view.getParent() != null) { - ((ViewGroup) view.getParent()).removeView(view); - } - frameLayout.addView(view); - - if (splashImageView.getParent() != null) { - ((ViewGroup) splashImageView.getParent()).removeView(splashImageView); - } - frameLayout.addView(splashImageView, lp); - - setContentView(frameLayout); - logger.log(Level.FINE, "Splash Screen Created"); - } else { - logger.log(Level.FINE, "Splash Screen Skipped."); - setContentView(view); - } - } - - public void removeSplashScreen() { - logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); - if (splashPicID != 0) { - if (frameLayout != null) { - if (splashImageView != null) { - this.runOnUiThread(new Runnable() { - @Override - public void run() { - splashImageView.setVisibility(View.INVISIBLE); - frameLayout.removeView(splashImageView); - } - }); - } else { - logger.log(Level.FINE, "splashImageView is null"); - } - } else { - logger.log(Level.FINE, "frameLayout is null"); - } - } - } - - /** - * Removes the standard Android log handler due to an issue with not logging - * entries lower than INFO level and adds a handler that produces - * JME formatted log messages. - */ - protected void initializeLogHandler() { - Logger log = LogManager.getLogManager().getLogger(""); - for (Handler handler : log.getHandlers()) { - if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) { - Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName()); - } - log.removeHandler(handler); - } - Handler handler = new AndroidLogHandler(); - log.addHandler(handler); - handler.setLevel(Level.ALL); - } - - public void initialize() { - app.initialize(); - if (handleExitHook) { - // remove existing mapping from SimpleApplication that stops the app - // when the esc key is pressed (esc key = android back key) so that - // AndroidHarness can produce the exit app dialog box. - if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) { - app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT); - } - - app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); - app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT}); - } - } - - public void reshape(int width, int height) { - app.reshape(width, height); - } - - public void update() { - app.update(); - // call to remove the splash screen, if present. - // call after app.update() to make sure no gap between - // splash screen going away and app display being shown. - if (firstDrawFrame) { - removeSplashScreen(); - firstDrawFrame = false; - } - } - - public void requestClose(boolean esc) { - app.requestClose(esc); - } - - public void destroy() { - if (app != null) { - app.destroy(); - } - if (finishOnAppStop) { - finish(); - } - } - - public void gainFocus() { - logger.fine("gainFocus"); - if (view != null) { - view.onResume(); - } - - if (app != null) { - //resume the audio - AudioRenderer audioRenderer = app.getAudioRenderer(); - if (audioRenderer != null) { - audioRenderer.resumeAll(); - } - //resume the sensors (aka joysticks) - if (app.getContext() != null) { - JoyInput joyInput = app.getContext().getJoyInput(); - if (joyInput != null) { - if (joyInput instanceof AndroidSensorJoyInput) { - AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; - androidJoyInput.resumeSensors(); - } - } - } - } - - isGLThreadPaused = false; - - if (app != null) { - app.gainFocus(); - } - } - - public void loseFocus() { - logger.fine("loseFocus"); - if (app != null) { - app.loseFocus(); - } - - if (view != null) { - view.onPause(); - } - - if (app != null) { - //pause the audio - AudioRenderer audioRenderer = app.getAudioRenderer(); - if (audioRenderer != null) { - audioRenderer.pauseAll(); - } - //pause the sensors (aka joysticks) - if (app.getContext() != null) { - JoyInput joyInput = app.getContext().getJoyInput(); - if (joyInput != null) { - if (joyInput instanceof AndroidSensorJoyInput) { - AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; - androidJoyInput.pauseSensors(); - } - } - } - } - isGLThreadPaused = true; - } -} +package com.jme3.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.pm.ActivityInfo; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.util.Log; +import android.view.*; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.JoyInput; +import com.jme3.input.TouchInput; +import com.jme3.input.android.AndroidSensorJoyInput; +import com.jme3.input.controls.TouchListener; +import com.jme3.input.controls.TouchTrigger; +import com.jme3.input.event.TouchEvent; +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.system.android.OGLESContext; +import com.jme3.util.AndroidLogHandler; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * AndroidHarness wraps a jme application object and runs it on + * Android + * + * @author Kirill + * @author larynx + */ +public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener, SystemListener { + + protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName()); + /** + * The application class to start + */ + protected String appClass = "jme3test.android.Test"; + /** + * The jme3 application object + */ + protected Application app = null; + + /** + * Sets the desired RGB size for the surfaceview. 16 = RGB565, 24 = RGB888. + * (default = 24) + */ + protected int eglBitsPerPixel = 24; + + /** + * Sets the desired number of Alpha bits for the surfaceview. This affects + * how the surfaceview is able to display Android views that are located + * under the surfaceview jME uses to render the scenegraph. + * 0 = Opaque surfaceview background (fastest) + * 1->7 = Transparent surfaceview background + * 8 or higher = Translucent surfaceview background + * (default = 0) + */ + protected int eglAlphaBits = 0; + + /** + * The number of depth bits specifies the precision of the depth buffer. + * (default = 16) + */ + protected int eglDepthBits = 16; + + /** + * Sets the number of samples to use for multisampling.
+ * Leave 0 (default) to disable multisampling.
+ * Set to 2 or 4 to enable multisampling. + */ + protected int eglSamples = 0; + + /** + * Set the number of stencil bits. + * (default = 0) + */ + protected int eglStencilBits = 0; + + /** + * Set the desired frame rate. If frameRate > 0, the application + * will be capped at the desired frame rate. + * (default = -1, no frame rate cap) + */ + protected int frameRate = -1; + + /** + * Sets the type of Audio Renderer to be used. + *

+ * Android MediaPlayer / SoundPool can be used on all + * supported Android platform versions (2.2+)
+ * OpenAL Soft uses an OpenSL backend and is only supported on Android + * versions 2.3+. + *

+ * Only use ANDROID_ static strings found in AppSettings + * + */ + protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + + /** + * If true Android Sensors are used as simulated Joysticks. Users can use the + * Android sensor feedback through the RawInputListener or by registering + * JoyAxisTriggers. + */ + protected boolean joystickEventsEnabled = false; + /** + * If true KeyEvents are generated from TouchEvents + */ + protected boolean keyEventsEnabled = true; + /** + * If true MouseEvents are generated from TouchEvents + */ + protected boolean mouseEventsEnabled = true; + /** + * Flip X axis + */ + protected boolean mouseEventsInvertX = false; + /** + * Flip Y axis + */ + protected boolean mouseEventsInvertY = false; + /** + * if true finish this activity when the jme app is stopped + */ + protected boolean finishOnAppStop = true; + /** + * set to false if you don't want the harness to handle the exit hook + */ + protected boolean handleExitHook = true; + /** + * Title of the exit dialog, default is "Do you want to exit?" + */ + protected String exitDialogTitle = "Do you want to exit?"; + /** + * Message of the exit dialog, default is "Use your home key to bring this + * app into the background or exit to terminate it." + */ + protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; + /** + * Set the screen window mode. If screenFullSize is true, then the + * notification bar and title bar are removed and the screen covers the + * entire display. If screenFullSize is false, then the notification bar + * remains visible if screenShowTitle is true while screenFullScreen is + * false, then the title bar is also displayed under the notification bar. + */ + protected boolean screenFullScreen = true; + /** + * if screenShowTitle is true while screenFullScreen is false, then the + * title bar is also displayed under the notification bar + */ + protected boolean screenShowTitle = true; + /** + * Splash Screen picture Resource ID. If a Splash Screen is desired, set + * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If + * splashPicID = 0, then no splash screen will be displayed. + */ + protected int splashPicID = 0; + + + protected OGLESContext ctx; + protected GLSurfaceView view = null; + protected boolean isGLThreadPaused = true; + protected ImageView splashImageView = null; + protected FrameLayout frameLayout = null; + final private String ESCAPE_EVENT = "TouchEscape"; + private boolean firstDrawFrame = true; + private boolean inConfigChange = false; + + private class DataObject { + protected Application app = null; + } + + @Override + public Object onRetainNonConfigurationInstance() { + logger.log(Level.FINE, "onRetainNonConfigurationInstance"); + final DataObject data = new DataObject(); + data.app = this.app; + inConfigChange = true; + return data; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + initializeLogHandler(); + + logger.fine("onCreate"); + super.onCreate(savedInstanceState); + + if (screenFullScreen) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + if (!screenShowTitle) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + } + } + + final DataObject data = (DataObject) getLastNonConfigurationInstance(); + if (data != null) { + logger.log(Level.FINE, "Using Retained App"); + this.app = data.app; + } else { + // Discover the screen reolution + //TODO try to find a better way to get a hand on the resolution + WindowManager wind = this.getWindowManager(); + Display disp = wind.getDefaultDisplay(); + Log.d("AndroidHarness", "Resolution from Window, width:" + disp.getWidth() + ", height: " + disp.getHeight()); + + // Create Settings + logger.log(Level.FINE, "Creating settings"); + AppSettings settings = new AppSettings(true); + settings.setEmulateMouse(mouseEventsEnabled); + settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY); + settings.setUseJoysticks(joystickEventsEnabled); + settings.setEmulateKeyboard(keyEventsEnabled); + + settings.setBitsPerPixel(eglBitsPerPixel); + settings.setAlphaBits(eglAlphaBits); + settings.setDepthBits(eglDepthBits); + settings.setSamples(eglSamples); + settings.setStencilBits(eglStencilBits); + + settings.setResolution(disp.getWidth(), disp.getHeight()); + settings.setAudioRenderer(audioRendererType); + + settings.setFrameRate(frameRate); + + // Create application instance + try { + if (app == null) { + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(appClass); + app = clazz.newInstance(); + } + + app.setSettings(settings); + app.start(); + } catch (Exception ex) { + handleError("Class " + appClass + " init failed", ex); + setContentView(new TextView(this)); + } + } + + ctx = (OGLESContext) app.getContext(); + view = ctx.createView(this); + // store the glSurfaceView in JmeAndroidSystem for future use + JmeAndroidSystem.setView(view); + // AndroidHarness wraps the app as a SystemListener. + ctx.setSystemListener(this); + layoutDisplay(); + } + + @Override + protected void onRestart() { + logger.fine("onRestart"); + super.onRestart(); + if (app != null) { + app.restart(); + } + } + + @Override + protected void onStart() { + logger.fine("onStart"); + super.onStart(); + } + + @Override + protected void onResume() { + logger.fine("onResume"); + super.onResume(); + + gainFocus(); + } + + @Override + protected void onPause() { + logger.fine("onPause"); + loseFocus(); + + super.onPause(); + } + + @Override + protected void onStop() { + logger.fine("onStop"); + super.onStop(); + } + + @Override + protected void onDestroy() { + logger.fine("onDestroy"); + final DataObject data = (DataObject) getLastNonConfigurationInstance(); + if (data != null || inConfigChange) { + logger.fine("In Config Change, not stopping app."); + } else { + if (app != null) { + app.stop(!isGLThreadPaused); + } + } + setContentView(new TextView(this)); + ctx = null; + app = null; + view = null; + JmeAndroidSystem.setView(null); + + super.onDestroy(); + } + + public Application getJmeApplication() { + return app; + } + + /** + * Called when an error has occurred. By default, will show an error message + * to the user and print the exception/error to the log. + */ + @Override + public void handleError(final String errorMsg, final Throwable t) { + String stackTrace = ""; + String title = "Error"; + + if (t != null) { + // Convert exception to string + StringWriter sw = new StringWriter(100); + t.printStackTrace(new PrintWriter(sw)); + stackTrace = sw.toString(); + title = t.toString(); + } + + final String finalTitle = title; + final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") + + "\n" + stackTrace; + + logger.log(Level.SEVERE, finalMsg); + + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) + .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create(); + dialog.show(); + } + }); + } + + /** + * Called by the android alert dialog, terminate the activity and OpenGL + * rendering + * + * @param dialog + * @param whichButton + */ + public void onClick(DialogInterface dialog, int whichButton) { + if (whichButton != -2) { + if (app != null) { + app.stop(true); + } + app = null; + this.finish(); + } + } + + /** + * Gets called by the InputManager on all touch/drag/scale events + */ + @Override + public void onTouch(String name, TouchEvent evt, float tpf) { + if (name.equals(ESCAPE_EVENT)) { + switch (evt.getType()) { + case KEY_UP: + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) + .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create(); + dialog.show(); + } + }); + break; + default: + break; + } + } + } + + public void layoutDisplay() { + logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); + if (view == null) { + logger.log(Level.FINE, "view is null!"); + } + if (splashPicID != 0) { + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT, + Gravity.CENTER); + + frameLayout = new FrameLayout(this); + splashImageView = new ImageView(this); + + Drawable drawable = this.getResources().getDrawable(splashPicID); + if (drawable instanceof NinePatchDrawable) { + splashImageView.setBackgroundDrawable(drawable); + } else { + splashImageView.setImageResource(splashPicID); + } + + if (view.getParent() != null) { + ((ViewGroup) view.getParent()).removeView(view); + } + frameLayout.addView(view); + + if (splashImageView.getParent() != null) { + ((ViewGroup) splashImageView.getParent()).removeView(splashImageView); + } + frameLayout.addView(splashImageView, lp); + + setContentView(frameLayout); + logger.log(Level.FINE, "Splash Screen Created"); + } else { + logger.log(Level.FINE, "Splash Screen Skipped."); + setContentView(view); + } + } + + public void removeSplashScreen() { + logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); + if (splashPicID != 0) { + if (frameLayout != null) { + if (splashImageView != null) { + this.runOnUiThread(new Runnable() { + @Override + public void run() { + splashImageView.setVisibility(View.INVISIBLE); + frameLayout.removeView(splashImageView); + } + }); + } else { + logger.log(Level.FINE, "splashImageView is null"); + } + } else { + logger.log(Level.FINE, "frameLayout is null"); + } + } + } + + /** + * Removes the standard Android log handler due to an issue with not logging + * entries lower than INFO level and adds a handler that produces + * JME formatted log messages. + */ + protected void initializeLogHandler() { + Logger log = LogManager.getLogManager().getLogger(""); + for (Handler handler : log.getHandlers()) { + if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) { + Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName()); + } + log.removeHandler(handler); + } + Handler handler = new AndroidLogHandler(); + log.addHandler(handler); + handler.setLevel(Level.ALL); + } + + public void initialize() { + app.initialize(); + if (handleExitHook) { + // remove existing mapping from SimpleApplication that stops the app + // when the esc key is pressed (esc key = android back key) so that + // AndroidHarness can produce the exit app dialog box. + if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) { + app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT); + } + + app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); + app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT}); + } + } + + public void reshape(int width, int height) { + app.reshape(width, height); + } + + public void update() { + app.update(); + // call to remove the splash screen, if present. + // call after app.update() to make sure no gap between + // splash screen going away and app display being shown. + if (firstDrawFrame) { + removeSplashScreen(); + firstDrawFrame = false; + } + } + + public void requestClose(boolean esc) { + app.requestClose(esc); + } + + public void destroy() { + if (app != null) { + app.destroy(); + } + if (finishOnAppStop) { + finish(); + } + } + + public void gainFocus() { + logger.fine("gainFocus"); + if (view != null) { + view.onResume(); + } + + if (app != null) { + //resume the audio + AudioRenderer audioRenderer = app.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.resumeAll(); + } + //resume the sensors (aka joysticks) + if (app.getContext() != null) { + JoyInput joyInput = app.getContext().getJoyInput(); + if (joyInput != null) { + if (joyInput instanceof AndroidSensorJoyInput) { + AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; + androidJoyInput.resumeSensors(); + } + } + } + } + + isGLThreadPaused = false; + + if (app != null) { + app.gainFocus(); + } + } + + public void loseFocus() { + logger.fine("loseFocus"); + if (app != null) { + app.loseFocus(); + } + + if (view != null) { + view.onPause(); + } + + if (app != null) { + //pause the audio + AudioRenderer audioRenderer = app.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.pauseAll(); + } + //pause the sensors (aka joysticks) + if (app.getContext() != null) { + JoyInput joyInput = app.getContext().getJoyInput(); + if (joyInput != null) { + if (joyInput instanceof AndroidSensorJoyInput) { + AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; + androidJoyInput.pauseSensors(); + } + } + } + } + isGLThreadPaused = true; + } +} From 5ab8fb8f6a8fafd8a27d3509928ceb427e2f24c3 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 13:09:49 -0400 Subject: [PATCH 049/225] Android: remove reference to screenOrientation from unused test project --- .../java/jme3test/android/DemoAndroidHarness.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java b/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java index 42c621a4f..61d0d6868 100644 --- a/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java +++ b/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java @@ -7,21 +7,19 @@ import com.jme3.app.AndroidHarness; public class DemoAndroidHarness extends AndroidHarness { @Override - public void onCreate(Bundle savedInstanceState) - { + public void onCreate(Bundle savedInstanceState) + { // Set the application class to run // First Extract the bundle from intent Bundle bundle = getIntent().getExtras(); //Next extract the values using the key as - appClass = bundle.getString("APPCLASSNAME"); - + appClass = bundle.getString("APPCLASSNAME"); + exitDialogTitle = "Close Demo?"; exitDialogMessage = "Press Yes"; - screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - - super.onCreate(savedInstanceState); + super.onCreate(savedInstanceState); } } From be6416b736326b1c3d6f77b8dd7d6141cf4364d9 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 13:12:08 -0400 Subject: [PATCH 050/225] SDK: Add layout.xml and strings.xml to Important Files node in Android projects. Useful to quickly modify the layout for the upcoming switch to Android Fragments. --- .../com/jme3/gde/android/AndroidImportantFiles.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sdk/jme3-android/src/com/jme3/gde/android/AndroidImportantFiles.java b/sdk/jme3-android/src/com/jme3/gde/android/AndroidImportantFiles.java index cbfb487cc..b96bb769c 100644 --- a/sdk/jme3-android/src/com/jme3/gde/android/AndroidImportantFiles.java +++ b/sdk/jme3-android/src/com/jme3/gde/android/AndroidImportantFiles.java @@ -67,6 +67,18 @@ public class AndroidImportantFiles implements ImportantFiles { node.setDisplayName("Android Properties"); list.add(node); } + FileObject layout = project.getProjectDirectory().getFileObject("mobile/res/layout/main.xml"); + if (layout != null) { + Node node = DataObject.find(layout).getNodeDelegate(); + node.setDisplayName("Android Layout"); + list.add(node); + } + FileObject strings = project.getProjectDirectory().getFileObject("mobile/res/values/strings.xml"); + if (strings != null) { + Node node = DataObject.find(strings).getNodeDelegate(); + node.setDisplayName("Android Strings"); + list.add(node); + } } catch (DataObjectNotFoundException ex) { Exceptions.printStackTrace(ex); } From 664dfc037cd84594f1e3506044af2d000fad69e6 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 13:14:16 -0400 Subject: [PATCH 051/225] SDK: Update creation of MainActivity and AndroidManifest to support jME 3.1 Add screen orientation to manifest and update the min version to 9. Update MainActivity creation to remove references to properties removed from AndroidHarness. --- .../com/jme3/gde/android/AndroidSdkTool.java | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java b/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java index b93b089cc..066265e74 100644 --- a/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java +++ b/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java @@ -299,17 +299,15 @@ public class AndroidSdkTool { changed = true; } } - // add the following after AndroidHarness.screenOrientation is depreciated - // for jME 3.1 -// if (sdkActivity != null) { -// if (sdkActivity.hasAttribute("android:screenOrientation")) { -// String attrScreenOrientation = sdkActivity.getAttribute("android:screenOrientation"); -// } else { -// Logger.getLogger(AndroidSdkTool.class.getName()).log(Level.INFO, "creating attrScreenOrientation"); -// sdkActivity.setAttribute("android:screenOrientation", "landscape"); -// changed = true; -// } -// } + if (sdkActivity != null) { + if (sdkActivity.hasAttribute("android:screenOrientation")) { + String attrScreenOrientation = sdkActivity.getAttribute("android:screenOrientation"); + } else { + Logger.getLogger(AndroidSdkTool.class.getName()).log(Level.INFO, "creating attrScreenOrientation"); + sdkActivity.setAttribute("android:screenOrientation", "landscape"); + changed = true; + } + } } Element sdkElement = XmlHelper.findChildElement(configuration.getDocumentElement(), "uses-sdk"); @@ -319,7 +317,7 @@ public class AndroidSdkTool { changed = true; } if (!"8".equals(sdkElement.getAttribute("android:minSdkVersion"))) { - sdkElement.setAttribute("android:minSdkVersion", "8"); + sdkElement.setAttribute("android:minSdkVersion", "9"); changed = true; } Element screensElement = XmlHelper.findChildElement(configuration.getDocumentElement(), "supports-screens"); @@ -419,7 +417,6 @@ public class AndroidSdkTool { + " \n" + "import android.content.pm.ActivityInfo;\n" + "import com.jme3.app.AndroidHarness;\n" - + "import com.jme3.system.android.AndroidConfigChooser.ConfigType;\n" + "import java.util.logging.Level;\n" + "import java.util.logging.LogManager;\n" + " \n" @@ -435,15 +432,9 @@ public class AndroidSdkTool { + " public MainActivity(){\n" + " // Set the application class to run\n" + " appClass = \"" + mainClass + "\";\n" - + " // Try ConfigType.FASTEST; or ConfigType.LEGACY if you have problems\n" - + " eglConfigType = ConfigType.BEST;\n" + " // Exit Dialog title & message\n" + " exitDialogTitle = \"Exit?\";\n" + " exitDialogMessage = \"Press Yes\";\n" - + " // Enable verbose logging\n" - + " eglConfigVerboseLogging = false;\n" - + " // Choose screen orientation\n" - + " screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;\n" + " // Enable MouseEvents being generated from TouchEvents (default = true)\n" + " mouseEventsEnabled = true;\n" + " // Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info)\n" From b095158bc0d29d4de539c1624e4c380d55b7da77 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 13:16:03 -0400 Subject: [PATCH 052/225] SDK: Add temporary fix to remove android.jar from mobile/libs folder during build. android.jar is being added to jme3-android.jar during the build. Build script of jme3-android project needs to be updated to not add android.jar to begin with. --- sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml b/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml index d57833296..2a302ee57 100644 --- a/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml +++ b/sdk/jme3-android/src/com/jme3/gde/android/mobile-targets.xml @@ -68,6 +68,8 @@ + + From d00c8a109f125892bfbaae6aa1d3f172051907d4 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 23:50:37 -0400 Subject: [PATCH 053/225] SDK: Switch android projects to use an Android Fragment --- .../com/jme3/gde/android/AndroidSdkTool.java | 139 ++++++++++++++++-- 1 file changed, 128 insertions(+), 11 deletions(-) diff --git a/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java b/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java index 066265e74..21340ab6e 100644 --- a/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java +++ b/sdk/jme3-android/src/com/jme3/gde/android/AndroidSdkTool.java @@ -243,6 +243,7 @@ public class AndroidSdkTool { } updateAndroidManifest(project); updateAndroidApplicationName(project, name); + updateAndroidLayout(project, packag); } public static void updateProject(Project project, String target, String name) { @@ -411,17 +412,79 @@ public class AndroidSdkTool { } } + private static void updateAndroidLayout(Project project, String packag) { + FileObject layout = project.getProjectDirectory().getFileObject("mobile/res/layout/main.xml"); + if (layout == null) { + Logger.getLogger(AndroidSdkTool.class.getName()).log(Level.WARNING, "Cannot find layout"); + return; + } + InputStream in = null; + FileLock lock = null; + OutputStream out = null; + try { + in = layout.getInputStream(); + Document configuration = XMLUtil.parse(new InputSource(in), false, false, null, null); + in.close(); + in = null; + + Element textViewElement = XmlHelper.findChildElement(configuration.getDocumentElement(), "TextView"); + + Element fragmentElement = configuration.createElement("fragment"); + fragmentElement.setAttribute("android:name", packag+".MainActivity$JmeFragment"); + fragmentElement.setAttribute("android:id", "@+id/jmeFragment"); + fragmentElement.setAttribute("android:layout_width", "match_parent"); + fragmentElement.setAttribute("android:layout_height", "match_parent"); + + if (textViewElement == null) { + configuration.getDocumentElement().appendChild(fragmentElement); + } else { + configuration.getDocumentElement().replaceChild(fragmentElement, textViewElement); + } + + lock = layout.lock(); + out = layout.getOutputStream(lock); + XMLUtil.write(configuration, out, "UTF-8"); + out.close(); + out = null; + lock.releaseLock(); + lock = null; + + } catch (SAXException ex) { + Exceptions.printStackTrace(ex); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } finally { + if (lock != null) { + lock.releaseLock(); + } + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException ex1) { + Exceptions.printStackTrace(ex1); + } + } + } + private static String mainActivityString(String mainClass, String packag) { String str = "package " + packag + ";\n" + " \n" - + "import android.content.pm.ActivityInfo;\n" - + "import com.jme3.app.AndroidHarness;\n" + + "import com.jme3.app.DefaultAndroidProfiler;\n" + + "import android.app.Activity;\n" + + "import android.app.FragmentManager;\n" + + "import android.os.Bundle;\n" + + "import android.view.Window;\n" + + "import android.view.WindowManager;\n" + + "import com.jme3.app.AndroidHarnessFragment;\n" + "import java.util.logging.Level;\n" + "import java.util.logging.LogManager;\n" + " \n" - + "public class MainActivity extends AndroidHarness{\n" - + " \n" + + "public class MainActivity extends Activity {\n" + " /*\n" + " * Note that you can ignore the errors displayed in this file,\n" + " * the android project will build regardless.\n" @@ -430,18 +493,72 @@ public class AndroidSdkTool { + " */\n" + " \n" + " public MainActivity(){\n" - + " // Set the application class to run\n" - + " appClass = \"" + mainClass + "\";\n" - + " // Exit Dialog title & message\n" - + " exitDialogTitle = \"Exit?\";\n" - + " exitDialogMessage = \"Press Yes\";\n" - + " // Enable MouseEvents being generated from TouchEvents (default = true)\n" - + " mouseEventsEnabled = true;\n" + " // Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info)\n" + " LogManager.getLogManager().getLogger(\"\").setLevel(Level.INFO);\n" + " }\n" + " \n" + + " @Override\n" + + " protected void onCreate(Bundle savedInstanceState) {\n" + + " super.onCreate(savedInstanceState);\n" + + " // Set window fullscreen and remove title bar\n" + + " requestWindowFeature(Window.FEATURE_NO_TITLE);\n" + + " getWindow().setFlags(\n" + + " WindowManager.LayoutParams.FLAG_FULLSCREEN,\n" + + " WindowManager.LayoutParams.FLAG_FULLSCREEN);\n" + + " setContentView(R.layout.main);\n" + + " \n" + + " // find the fragment\n" + + " FragmentManager fm = getFragmentManager();\n" + + " AndroidHarnessFragment jmeFragment =\n" + + " (AndroidHarnessFragment) fm.findFragmentById(R.id.jmeFragment);\n" + + " \n" + + " // uncomment the next line to add the default android profiler to the project\n" + + " //jmeFragment.getJmeApplication().setAppProfiler(new DefaultAndroidProfiler());\n" + + " }\n" + + " \n" + + " \n" + + " public static class JmeFragment extends AndroidHarnessFragment {\n" + + " public JmeFragment() {\n" + + " // Set main project class (fully qualified path)\n" + + " appClass = \"" + mainClass + "\";\n" + + " \n" + + " // Set the desired EGL configuration\n" + + " eglBitsPerPixel = 24;\n" + + " eglAlphaBits = 0;\n" + + " eglDepthBits = 16;\n" + + " eglSamples = 0;\n" + + " eglStencilBits = 0;\n" + + " \n" + + " // Set the maximum framerate\n" + + " // (default = -1 for unlimited)\n" + + " frameRate = -1;\n" + + " \n" + + " // Set the maximum resolution dimension\n" + + " // (the smaller side, height or width, is set automatically\n" + + " // to maintain the original device screen aspect ratio)\n" + + " // (default = -1 to match device screen resolution)\n" + + " maxResolutionDimension = -1;\n" + + " \n" + + " // Set input configuration settings\n" + + " joystickEventsEnabled = false;\n" + + " keyEventsEnabled = true;\n" + + " mouseEventsEnabled = true;\n" + + " \n" + + " // Set application exit settings\n" + + " finishOnAppStop = true;\n" + + " handleExitHook = true;\n" + + " exitDialogTitle = \"Do you want to exit?\";\n" + + " exitDialogMessage = \"Use your home key to bring this app into the background or exit to terminate it.\";\n" + + " \n" + + " // Set splash screen resource id, if used\n" + + " // (default = 0, no splash screen)\n" + + " // For example, if the image file name is \"splash\"...\n" + + " // splashPicID = R.drawable.splash;\n" + + " splashPicID = 0;\n" + + " }\n" + + " }\n" + "}\n"; + return str; } From 4102e91456df871530613e647474155fc9a5ea2c Mon Sep 17 00:00:00 2001 From: iwgeric Date: Thu, 2 Apr 2015 23:51:26 -0400 Subject: [PATCH 054/225] Android: remove unused properties in AndroidHarnessFragment --- .../java/com/jme3/app/AndroidHarnessFragment.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java index 1b205d6dd..5673c8609 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java @@ -195,19 +195,6 @@ public class AndroidHarnessFragment extends Fragment implements */ protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; - /** - * Set the screen window mode. If screenFullSize is true, then the - * notification bar and title bar are removed and the screen covers the - * entire display. If screenFullSize is false, then the notification bar - * remains visible if screenShowTitle is true while screenFullScreen is - * false, then the title bar is also displayed under the notification bar. - */ - protected boolean screenFullScreen = true; - /** - * if screenShowTitle is true while screenFullScreen is false, then the - * title bar is also displayed under the notification bar - */ - protected boolean screenShowTitle = true; /** * Splash Screen picture Resource ID. If a Splash Screen is desired, set * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If From 5b56f1e513a9de16cf14e2f5b446c903588b028f Mon Sep 17 00:00:00 2001 From: Julien Gouesse Date: Sat, 4 Apr 2015 16:37:36 +0200 Subject: [PATCH 055/225] Updates the JogAmp backend, uses JOGL 2.3.1 --- jme3-jogl/build.gradle | 6 ++-- .../com/jme3/input/jogl/NewtMouseInput.java | 10 +++---- .../com/jme3/renderer/jogl/JoglRenderer.java | 22 +++++++-------- .../com/jme3/renderer/jogl/TextureUtil.java | 19 +++++++------ .../jme3/system/jogl/JoglAbstractDisplay.java | 28 +++++++++---------- .../java/com/jme3/system/jogl/JoglCanvas.java | 2 +- .../com/jme3/system/jogl/JoglContext.java | 6 ++-- .../com/jme3/system/jogl/JoglDisplay.java | 2 +- .../system/jogl/JoglNewtAbstractDisplay.java | 26 ++++++++--------- .../com/jme3/system/jogl/JoglNewtCanvas.java | 2 +- .../com/jme3/system/jogl/JoglNewtDisplay.java | 4 +-- .../jme3/system/jogl/JoglOffscreenBuffer.java | 12 ++++---- 12 files changed, 71 insertions(+), 68 deletions(-) diff --git a/jme3-jogl/build.gradle b/jme3-jogl/build.gradle index 135656ee6..e82139729 100644 --- a/jme3-jogl/build.gradle +++ b/jme3-jogl/build.gradle @@ -5,7 +5,7 @@ if (!hasProperty('mainClass')) { dependencies { compile project(':jme3-core') compile project(':jme3-desktop') - compile 'org.jogamp.gluegen:gluegen-rt-main:2.2.0' - compile 'org.jogamp.jogl:jogl-all-main:2.2.0' - compile 'org.jogamp.joal:joal-main:2.2.0' + compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.1' + compile 'org.jogamp.jogl:jogl-all-main:2.3.1' + compile 'org.jogamp.joal:joal-main:2.3.1' } diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java index d50017ccc..773f62b8a 100644 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java @@ -45,11 +45,11 @@ import com.jogamp.newt.opengl.GLWindow; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.logging.Logger; -import javax.media.nativewindow.util.Dimension; -import javax.media.nativewindow.util.DimensionImmutable; -import javax.media.nativewindow.util.PixelFormat; -import javax.media.nativewindow.util.PixelRectangle; -import javax.media.nativewindow.util.Point; +import com.jogamp.nativewindow.util.Dimension; +import com.jogamp.nativewindow.util.DimensionImmutable; +import com.jogamp.nativewindow.util.PixelFormat; +import com.jogamp.nativewindow.util.PixelRectangle; +import com.jogamp.nativewindow.util.Point; public class NewtMouseInput implements MouseInput, MouseListener { diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java index 85e5c7102..111719265 100644 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java @@ -70,15 +70,15 @@ import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import javax.media.nativewindow.NativeWindowFactory; -import javax.media.opengl.GL; -import javax.media.opengl.GL2; -import javax.media.opengl.GL2ES1; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GL2ES3; -import javax.media.opengl.GL2GL3; -import javax.media.opengl.GL3; -import javax.media.opengl.GLContext; +import com.jogamp.nativewindow.NativeWindowFactory; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL2ES1; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GL2ES3; +import com.jogamp.opengl.GL2GL3; +import com.jogamp.opengl.GL3; +import com.jogamp.opengl.GLContext; import jme3tools.converters.MipMapGenerator; import jme3tools.shader.ShaderDebug; @@ -1800,7 +1800,7 @@ public class JoglRenderer implements Renderer { if (samples > 1) { return GL3.GL_TEXTURE_2D_MULTISAMPLE_ARRAY; } else { - return GL.GL_TEXTURE_2D_ARRAY; + return GL2ES3.GL_TEXTURE_2D_ARRAY; } case ThreeDimensional: return GL2ES2.GL_TEXTURE_3D; @@ -2014,7 +2014,7 @@ public class JoglRenderer implements Renderer { for (int i = 0; i < 6; i++) { TextureUtil.uploadTexture(img, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, linearizeSrgbImages); } - } else if (target == GL.GL_TEXTURE_2D_ARRAY) { + } else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY) { if (!caps.contains(Caps.TextureArray)) { throw new RendererException("Texture arrays not supported by graphics hardware"); } diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java index 9bdc0ca2d..8f3c5b156 100644 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java @@ -36,14 +36,17 @@ import com.jme3.renderer.RendererException; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.image.ColorSpace; + import java.nio.ByteBuffer; import java.util.logging.Level; import java.util.logging.Logger; -import javax.media.opengl.GL; -import javax.media.opengl.GL2; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GL2GL3; -import javax.media.opengl.GLContext; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GL2ES3; +import com.jogamp.opengl.GL2GL3; +import com.jogamp.opengl.GLContext; public class TextureUtil { @@ -124,9 +127,9 @@ public class TextureUtil { setFormat(Format.RGB32F, GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false); // Special RGB formats - setFormat(Format.RGB111110F, GL.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false); + setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false); setFormat(Format.RGB9E5, GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false); - setFormat(Format.RGB16F_to_RGB111110F, GL.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); + setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false); // RGBA formats @@ -346,7 +349,7 @@ public class TextureUtil { glFmt.format, glFmt.dataType, data); - }else if (target == GL.GL_TEXTURE_2D_ARRAY){ + }else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){ // prepare data for 2D array // or upload slice if (index == -1){ diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java index 8c6ec6e55..a7f7fbfd7 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java @@ -45,20 +45,20 @@ import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; -import javax.media.opengl.DebugGL2; -import javax.media.opengl.DebugGL3; -import javax.media.opengl.DebugGL3bc; -import javax.media.opengl.DebugGL4; -import javax.media.opengl.DebugGL4bc; -import javax.media.opengl.DebugGLES1; -import javax.media.opengl.DebugGLES2; -import javax.media.opengl.GL; -import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLCapabilities; -import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLProfile; -import javax.media.opengl.GLRunnable; -import javax.media.opengl.awt.GLCanvas; +import com.jogamp.opengl.DebugGL2; +import com.jogamp.opengl.DebugGL3; +import com.jogamp.opengl.DebugGL3bc; +import com.jogamp.opengl.DebugGL4; +import com.jogamp.opengl.DebugGL4bc; +import com.jogamp.opengl.DebugGLES1; +import com.jogamp.opengl.DebugGLES2; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.GLRunnable; +import com.jogamp.opengl.awt.GLCanvas; public abstract class JoglAbstractDisplay extends JoglContext implements GLEventListener { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java index c4e97570e..9a409b85e 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java @@ -35,7 +35,7 @@ package com.jme3.system.jogl; import com.jme3.system.JmeCanvasContext; import java.awt.Canvas; import java.util.logging.Logger; -import javax.media.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLAutoDrawable; public class JoglCanvas extends JoglAbstractDisplay implements JmeCanvasContext { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java index 8861904b8..5d687af8d 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java @@ -47,9 +47,9 @@ import java.nio.IntBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import javax.media.opengl.GL; -import javax.media.opengl.GL2GL3; -import javax.media.opengl.GLContext; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2GL3; +import com.jogamp.opengl.GLContext; public abstract class JoglContext implements JmeContext { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java index 363ccd096..3bac0ab53 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java @@ -45,7 +45,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import javax.media.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLAutoDrawable; import javax.swing.JFrame; import javax.swing.SwingUtilities; diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java index 4899c9c39..cb75d5d37 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java @@ -44,19 +44,19 @@ import com.jogamp.opengl.util.AnimatorBase; import com.jogamp.opengl.util.FPSAnimator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; -import javax.media.opengl.DebugGL2; -import javax.media.opengl.DebugGL3; -import javax.media.opengl.DebugGL3bc; -import javax.media.opengl.DebugGL4; -import javax.media.opengl.DebugGL4bc; -import javax.media.opengl.DebugGLES1; -import javax.media.opengl.DebugGLES2; -import javax.media.opengl.GL; -import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLCapabilities; -import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLProfile; -import javax.media.opengl.GLRunnable; +import com.jogamp.opengl.DebugGL2; +import com.jogamp.opengl.DebugGL3; +import com.jogamp.opengl.DebugGL3bc; +import com.jogamp.opengl.DebugGL4; +import com.jogamp.opengl.DebugGL4bc; +import com.jogamp.opengl.DebugGLES1; +import com.jogamp.opengl.DebugGLES2; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.GLRunnable; public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLEventListener { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java index e4f46d8fb..3ed501580 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java @@ -35,7 +35,7 @@ package com.jme3.system.jogl; import com.jme3.system.JmeCanvasContext; import com.jogamp.newt.awt.NewtCanvasAWT; import java.util.logging.Logger; -import javax.media.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLAutoDrawable; public class JoglNewtCanvas extends JoglNewtAbstractDisplay implements JmeCanvasContext { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java index 011002aff..84a99e8d2 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java @@ -42,8 +42,8 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import javax.media.nativewindow.util.Dimension; -import javax.media.opengl.GLAutoDrawable; +import com.jogamp.nativewindow.util.Dimension; +import com.jogamp.opengl.GLAutoDrawable; public class JoglNewtDisplay extends JoglNewtAbstractDisplay { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java index e2ad1f79a..cd2b84f73 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java @@ -41,12 +41,12 @@ import com.jogamp.newt.NewtVersion; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import javax.media.opengl.GL; -import javax.media.opengl.GLCapabilities; -import javax.media.opengl.GLContext; -import javax.media.opengl.GLDrawableFactory; -import javax.media.opengl.GLOffscreenAutoDrawable; -import javax.media.opengl.GLProfile; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.GLDrawableFactory; +import com.jogamp.opengl.GLOffscreenAutoDrawable; +import com.jogamp.opengl.GLProfile; public class JoglOffscreenBuffer extends JoglContext implements Runnable { From 9f32bcf7bb116afcfcc8bcacd3bdfbdc18fb2bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 7 Apr 2015 01:26:13 +0200 Subject: [PATCH 056/225] Hack fix for blenderloader. The optimisation disabled would result in null TextureKeys. -> This would then result in textures being embedded into the j3o file, seriously bloating the assets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kai Börnert --- .../scene/plugins/blender/textures/TextureHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java index f6ed89ce7..f1c1c64fc 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -232,7 +232,7 @@ public class TextureHelper extends AbstractBlenderHelper { LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); Texture result = null; Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (im == null) { + // if (im == null) { HACK force reaload always, as constructor in else case is destroying the TextureKeys! if ("ID".equals(imageStructure.getType())) { LOGGER.fine("Loading texture from external blend file."); result = (Texture) this.loadLibrary(imageStructure); @@ -253,9 +253,9 @@ public class TextureHelper extends AbstractBlenderHelper { result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); } } - } else { - result = new Texture2D(im); - } + //} else { + // result = new Texture2D(im); + // } if (result != null) {// render result is not being loaded blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure); From 47e9b9ba16adc7d0cebf1f453936f9ae704c13cf Mon Sep 17 00:00:00 2001 From: iwgeric Date: Tue, 7 Apr 2015 17:53:54 -0400 Subject: [PATCH 057/225] Android: Remove unused property to track if joysticks are enabled. --- .../input/android/AndroidInputHandler.java | 528 +++++++++--------- 1 file changed, 263 insertions(+), 265 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java index 592baecaf..202907316 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -1,265 +1,263 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.input.android; - -import android.opengl.GLSurfaceView; -import android.os.Build; -import android.view.View; -import com.jme3.input.RawInputListener; -import com.jme3.input.TouchInput; -import com.jme3.input.event.InputEvent; -import com.jme3.input.event.KeyInputEvent; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; -import com.jme3.input.event.TouchEvent; -import com.jme3.system.AppSettings; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * AndroidInput is the main class that connects the Android system - * inputs to jME. It serves as the manager that gathers inputs from the various - * Android input methods and provides them to jME's InputManager. - * - * @author iwgeric - */ -public class AndroidInputHandler implements TouchInput { - private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); - - // Custom settings - private boolean mouseEventsEnabled = true; - private boolean mouseEventsInvertX = false; - private boolean mouseEventsInvertY = false; - private boolean keyboardEventsEnabled = false; - private boolean joystickEventsEnabled = false; - private boolean dontSendHistory = false; - - - // Internal - private GLSurfaceView view; - private AndroidTouchHandler touchHandler; - private AndroidKeyHandler keyHandler; - private AndroidGestureHandler gestureHandler; - private boolean initialized = false; - private RawInputListener listener = null; - private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); - private final static int MAX_TOUCH_EVENTS = 1024; - private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); - private float scaleX = 1f; - private float scaleY = 1f; - - - public AndroidInputHandler() { - int buildVersion = Build.VERSION.SDK_INT; - logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); - if (buildVersion >= 14) { - // add support for onHover and GenericMotionEvent (ie. gamepads) - gestureHandler = new AndroidGestureHandler(this); - touchHandler = new AndroidTouchHandler14(this, gestureHandler); - keyHandler = new AndroidKeyHandler(this); - } else if (buildVersion >= 8){ - gestureHandler = new AndroidGestureHandler(this); - touchHandler = new AndroidTouchHandler(this, gestureHandler); - keyHandler = new AndroidKeyHandler(this); - } - } - - public AndroidInputHandler(AndroidTouchHandler touchInput, - AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) { - this.touchHandler = touchInput; - this.keyHandler = keyInput; - this.gestureHandler = gestureHandler; - } - - public void setView(View view) { - if (touchHandler != null) { - touchHandler.setView(view); - } - if (keyHandler != null) { - keyHandler.setView(view); - } - if (gestureHandler != null) { - gestureHandler.setView(view); - } - this.view = (GLSurfaceView)view; - } - - public View getView() { - return view; - } - - public float invertX(float origX) { - return getJmeX(view.getWidth()) - origX; - } - - public float invertY(float origY) { - return getJmeY(view.getHeight()) - origY; - } - - public float getJmeX(float origX) { - return origX * scaleX; - } - - public float getJmeY(float origY) { - return origY * scaleY; - } - - public void loadSettings(AppSettings settings) { - keyboardEventsEnabled = settings.isEmulateKeyboard(); - mouseEventsEnabled = settings.isEmulateMouse(); - mouseEventsInvertX = settings.isEmulateMouseFlipX(); - mouseEventsInvertY = settings.isEmulateMouseFlipY(); - joystickEventsEnabled = settings.useJoysticks(); - - // view width and height are 0 until the view is displayed on the screen - if (view.getWidth() != 0 && view.getHeight() != 0) { - scaleX = (float)settings.getWidth() / (float)view.getWidth(); - scaleY = (float)settings.getHeight() / (float)view.getHeight(); - } - logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", - new Object[]{scaleX, scaleY}); - - } - - // ----------------------------------------- - // JME3 Input interface - @Override - public void initialize() { - touchEventPool.initialize(); - if (touchHandler != null) { - touchHandler.initialize(); - } - if (keyHandler != null) { - keyHandler.initialize(); - } - if (gestureHandler != null) { - gestureHandler.initialize(); - } - - initialized = true; - } - - @Override - public void destroy() { - initialized = false; - - touchEventPool.destroy(); - if (touchHandler != null) { - touchHandler.destroy(); - } - if (keyHandler != null) { - keyHandler.destroy(); - } - if (gestureHandler != null) { - gestureHandler.destroy(); - } - - setView(null); - } - - @Override - public boolean isInitialized() { - return initialized; - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return System.nanoTime(); - } - - public void update() { - if (listener != null) { - InputEvent inputEvent; - - while ((inputEvent = inputEventQueue.poll()) != null) { - if (inputEvent instanceof TouchEvent) { - listener.onTouchEvent((TouchEvent)inputEvent); - } else if (inputEvent instanceof MouseButtonEvent) { - listener.onMouseButtonEvent((MouseButtonEvent)inputEvent); - } else if (inputEvent instanceof MouseMotionEvent) { - listener.onMouseMotionEvent((MouseMotionEvent)inputEvent); - } else if (inputEvent instanceof KeyInputEvent) { - listener.onKeyEvent((KeyInputEvent)inputEvent); - } - } - } - } - - // ----------------------------------------- - - public TouchEvent getFreeTouchEvent() { - return touchEventPool.getNextFreeEvent(); - } - - public void addEvent(InputEvent event) { - inputEventQueue.add(event); - if (event instanceof TouchEvent) { - touchEventPool.storeEvent((TouchEvent)event); - } - } - - public void setSimulateMouse(boolean simulate) { - this.mouseEventsEnabled = simulate; - } - - public boolean isSimulateMouse() { - return mouseEventsEnabled; - } - - public boolean isMouseEventsInvertX() { - return mouseEventsInvertX; - } - - public boolean isMouseEventsInvertY() { - return mouseEventsInvertY; - } - - public void setSimulateKeyboard(boolean simulate) { - this.keyboardEventsEnabled = simulate; - } - - public boolean isSimulateKeyboard() { - return keyboardEventsEnabled; - } - - public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; - } - -} +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.opengl.GLSurfaceView; +import android.os.Build; +import android.view.View; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.system.AppSettings; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidInput is the main class that connects the Android system + * inputs to jME. It serves as the manager that gathers inputs from the various + * Android input methods and provides them to jME's InputManager. + * + * @author iwgeric + */ +public class AndroidInputHandler implements TouchInput { + private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); + + // Custom settings + private boolean mouseEventsEnabled = true; + private boolean mouseEventsInvertX = false; + private boolean mouseEventsInvertY = false; + private boolean keyboardEventsEnabled = false; + private boolean dontSendHistory = false; + + + // Internal + private GLSurfaceView view; + private AndroidTouchHandler touchHandler; + private AndroidKeyHandler keyHandler; + private AndroidGestureHandler gestureHandler; + private boolean initialized = false; + private RawInputListener listener = null; + private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + private final static int MAX_TOUCH_EVENTS = 1024; + private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); + private float scaleX = 1f; + private float scaleY = 1f; + + + public AndroidInputHandler() { + int buildVersion = Build.VERSION.SDK_INT; + logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); + if (buildVersion >= 14) { + // add support for onHover and GenericMotionEvent (ie. gamepads) + gestureHandler = new AndroidGestureHandler(this); + touchHandler = new AndroidTouchHandler14(this, gestureHandler); + keyHandler = new AndroidKeyHandler(this); + } else if (buildVersion >= 8){ + gestureHandler = new AndroidGestureHandler(this); + touchHandler = new AndroidTouchHandler(this, gestureHandler); + keyHandler = new AndroidKeyHandler(this); + } + } + + public AndroidInputHandler(AndroidTouchHandler touchInput, + AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) { + this.touchHandler = touchInput; + this.keyHandler = keyInput; + this.gestureHandler = gestureHandler; + } + + public void setView(View view) { + if (touchHandler != null) { + touchHandler.setView(view); + } + if (keyHandler != null) { + keyHandler.setView(view); + } + if (gestureHandler != null) { + gestureHandler.setView(view); + } + this.view = (GLSurfaceView)view; + } + + public View getView() { + return view; + } + + public float invertX(float origX) { + return getJmeX(view.getWidth()) - origX; + } + + public float invertY(float origY) { + return getJmeY(view.getHeight()) - origY; + } + + public float getJmeX(float origX) { + return origX * scaleX; + } + + public float getJmeY(float origY) { + return origY * scaleY; + } + + public void loadSettings(AppSettings settings) { + keyboardEventsEnabled = settings.isEmulateKeyboard(); + mouseEventsEnabled = settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + + // view width and height are 0 until the view is displayed on the screen + if (view.getWidth() != 0 && view.getHeight() != 0) { + scaleX = (float)settings.getWidth() / (float)view.getWidth(); + scaleY = (float)settings.getHeight() / (float)view.getHeight(); + } + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", + new Object[]{scaleX, scaleY}); + + } + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + touchEventPool.initialize(); + if (touchHandler != null) { + touchHandler.initialize(); + } + if (keyHandler != null) { + keyHandler.initialize(); + } + if (gestureHandler != null) { + gestureHandler.initialize(); + } + + initialized = true; + } + + @Override + public void destroy() { + initialized = false; + + touchEventPool.destroy(); + if (touchHandler != null) { + touchHandler.destroy(); + } + if (keyHandler != null) { + keyHandler.destroy(); + } + if (gestureHandler != null) { + gestureHandler.destroy(); + } + + setView(null); + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void update() { + if (listener != null) { + InputEvent inputEvent; + + while ((inputEvent = inputEventQueue.poll()) != null) { + if (inputEvent instanceof TouchEvent) { + listener.onTouchEvent((TouchEvent)inputEvent); + } else if (inputEvent instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent)inputEvent); + } else if (inputEvent instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent)inputEvent); + } else if (inputEvent instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent)inputEvent); + } + } + } + } + + // ----------------------------------------- + + public TouchEvent getFreeTouchEvent() { + return touchEventPool.getNextFreeEvent(); + } + + public void addEvent(InputEvent event) { + inputEventQueue.add(event); + if (event instanceof TouchEvent) { + touchEventPool.storeEvent((TouchEvent)event); + } + } + + public void setSimulateMouse(boolean simulate) { + this.mouseEventsEnabled = simulate; + } + + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public void setSimulateKeyboard(boolean simulate) { + this.keyboardEventsEnabled = simulate; + } + + public boolean isSimulateKeyboard() { + return keyboardEventsEnabled; + } + + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + +} From 22d3f7f9f462c539ea03559962be8b12425bf84e Mon Sep 17 00:00:00 2001 From: iwgeric Date: Tue, 7 Apr 2015 18:35:48 -0400 Subject: [PATCH 058/225] Android: Refactor joystick support to prepare for upcoming gamepad support. --- .../input/android/AndroidJoyInputHandler.java | 257 ++++++++++++++++++ .../input/android/AndroidSensorJoyInput.java | 176 ++++-------- .../com/jme3/system/android/OGLESContext.java | 20 +- 3 files changed, 315 insertions(+), 138 deletions(-) create mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java new file mode 100644 index 000000000..2c3a1d74e --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java @@ -0,0 +1,257 @@ +/* + * 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.input.android; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.os.Build; +import android.os.Vibrator; +import android.view.View; +import com.jme3.input.InputManager; +import com.jme3.input.JoyInput; +import com.jme3.input.Joystick; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.system.AppSettings; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Main class that manages various joystick devices. Joysticks can be many forms + * including a simulated joystick to communicate the device orientation as well + * as physical gamepads.
+ * This class manages all the joysticks and feeds the inputs from each back + * to jME's InputManager. + * + * This handler also supports the joystick.rumble(rumbleAmount) method. In this + * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate + * if the device has a built in vibrate motor. + * + * Because Andorid does not allow for the user to define the intensity of the + * vibration, the rumble amount (ie strength) is converted into vibration pulses + * The stronger the strength amount, the shorter the delay between pulses. If + * amount is 1, then the vibration stays on the whole time. If amount is 0.5, + * the vibration will a pulse of equal parts vibration and delay. + * To turn off vibration, set rumble amount to 0. + * + * MainActivity needs the following line to enable Joysticks on Android platforms + * joystickEventsEnabled = true; + * This is done to allow for battery conservation when sensor data or gamepads + * are not required by the application. + * + * To use the joystick rumble feature, the following line needs to be + * added to the Android Manifest File + * + * + * @author iwgeric + */ +public class AndroidJoyInputHandler implements JoyInput { + private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName()); + + private List joystickList = new ArrayList(); +// private boolean dontSendHistory = false; + + + // Internal + private GLSurfaceView view; + private boolean initialized = false; + private RawInputListener listener = null; + private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue(); + private AndroidSensorJoyInput sensorJoyInput; + private Vibrator vibrator = null; + private boolean vibratorActive = false; + private long maxRumbleTime = 250; // 250ms + + public AndroidJoyInputHandler() { + int buildVersion = Build.VERSION.SDK_INT; + logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); +// if (buildVersion >= 14) { +// touchHandler = new AndroidTouchHandler14(this); +// } else if (buildVersion >= 8){ +// touchHandler = new AndroidTouchHandler(this); +// } + sensorJoyInput = new AndroidSensorJoyInput(this); + } + + public void setView(GLSurfaceView view) { +// if (touchHandler != null) { +// touchHandler.setView(view); +// } + if (sensorJoyInput != null) { + sensorJoyInput.setView(view); + } + this.view = (GLSurfaceView)view; + + } + + public View getView() { + return view; + } + + public void loadSettings(AppSettings settings) { +// sensorEventsEnabled = settings.useSensors(); + } + + public void addEvent(InputEvent event) { + eventQueue.add(event); + } + + /** + * Pauses the joystick device listeners to save battery life if they are not needed. + * Used to pause when the activity pauses + */ + public void pauseJoysticks() { + if (sensorJoyInput != null) { + sensorJoyInput.pauseSensors(); + } + if (vibrator != null && vibratorActive) { + vibrator.cancel(); + } + + } + + /** + * Resumes the joystick device listeners. + * Used to resume when the activity comes to the top of the stack + */ + public void resumeJoysticks() { + if (sensorJoyInput != null) { + sensorJoyInput.resumeSensors(); + } + + } + + + + + + @Override + public void initialize() { +// if (sensorJoyInput != null) { +// sensorJoyInput.initialize(); +// } + // Get instance of Vibrator from current Context + vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null) { + logger.log(Level.FINE, "Vibrator Service not found."); + } + initialized = true; + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void destroy() { + initialized = false; + + if (sensorJoyInput != null) { + sensorJoyInput.destroy(); + } + + setView(null); + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + + @Override + public void setJoyRumble(int joyId, float amount) { + // convert amount to pulses since Android doesn't allow intensity + if (vibrator != null) { + final long rumbleOnDur = (long)(amount * maxRumbleTime); // ms to pulse vibration on + final long rumbleOffDur = maxRumbleTime - rumbleOnDur; // ms to delay between pulses + final long[] rumblePattern = { + 0, // start immediately + rumbleOnDur, // time to leave vibration on + rumbleOffDur // time to delay between vibrations + }; + final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from + + logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", + new Object[]{amount, rumbleOnDur, rumbleOffDur}); + + if (rumbleOnDur > 0) { + vibrator.vibrate(rumblePattern, rumbleRepeatFrom); + vibratorActive = true; + } else { + vibrator.cancel(); + vibratorActive = false; + } + } + } + + @Override + public Joystick[] loadJoysticks(InputManager inputManager) { + joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); + + + return joystickList.toArray( new Joystick[joystickList.size()] ); + } + + @Override + public void update() { + if (sensorJoyInput != null) { + sensorJoyInput.update(); + } + + if (listener != null) { + InputEvent inputEvent; + + while ((inputEvent = eventQueue.poll()) != null) { + if (inputEvent instanceof JoyAxisEvent) { + listener.onJoyAxisEvent((JoyAxisEvent)inputEvent); + } else if (inputEvent instanceof JoyButtonEvent) { + listener.onJoyButtonEvent((JoyButtonEvent)inputEvent); + } + } + } + + } + + + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java index 034b068d8..823aa3d9d 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java @@ -37,7 +37,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.os.Vibrator; +import android.opengl.GLSurfaceView; import android.view.Surface; import android.view.WindowManager; import com.jme3.input.AbstractJoystick; @@ -47,10 +47,8 @@ import com.jme3.input.JoyInput; import com.jme3.input.Joystick; import com.jme3.input.JoystickAxis; import com.jme3.input.SensorJoystickAxis; -import com.jme3.input.RawInputListener; import com.jme3.input.event.JoyAxisEvent; import com.jme3.math.FastMath; -import com.jme3.system.android.JmeAndroidSystem; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; import java.util.ArrayList; @@ -63,7 +61,7 @@ import java.util.logging.Logger; * A single joystick is configured and includes data for all configured sensors * as seperate axes of the joystick. * - * Each axis is named accounting to the static strings in SensorJoystickAxis. + * Each axis is named according to the static strings in SensorJoystickAxis. * Refer to the strings defined in SensorJoystickAxis for a list of supported * sensors and their axis data. Each sensor type defined in SensorJoystickAxis * will be attempted to be configured. If the device does not support a particular @@ -72,46 +70,21 @@ import java.util.logging.Logger; * The joystick.getXAxis and getYAxis methods of the joystick are configured to * return the device orientation values in the device's X and Y directions. * - * This joystick also supports the joystick.rumble(rumbleAmount) method. In this - * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate - * if the device has a built in vibrate motor. - * - * Because Andorid does not allow for the user to define the intensity of the - * vibration, the rumble amount (ie strength) is converted into vibration pulses - * The stronger the strength amount, the shorter the delay between pulses. If - * amount is 1, then the vibration stays on the whole time. If amount is 0.5, - * the vibration will a pulse of equal parts vibration and delay. - * To turn off vibration, set rumble amount to 0. - * - * MainActivity needs the following line to enable Joysticks on Android platforms - * joystickEventsEnabled = true; - * This is done to allow for battery conservation when sensor data is not required - * by the application. - * - * To use the joystick rumble feature, the following line needs to be - * added to the Android Manifest File - * - * * @author iwgeric */ -public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { +public class AndroidSensorJoyInput implements SensorEventListener { private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName()); - private Context context = null; - private InputManager inputManager = null; + private AndroidJoyInputHandler joyHandler; private SensorManager sensorManager = null; private WindowManager windowManager = null; - private Vibrator vibrator = null; - private boolean vibratorActive = false; - private long maxRumbleTime = 250; // 250ms - private RawInputListener listener = null; private IntMap sensors = new IntMap(); - private AndroidJoystick[] joysticks; private int lastRotation = 0; - private boolean initialized = false; private boolean loaded = false; - private final ArrayList eventQueue = new ArrayList(); + public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) { + this.joyHandler = joyHandler; + } /** * Internal class to enclose data for each sensor. @@ -120,7 +93,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { int androidSensorType = -1; int androidSensorSpeed = SensorManager.SENSOR_DELAY_GAME; Sensor sensor = null; - int sensorAccuracy = 0; + int sensorAccuracy = -1; float[] lastValues; final Object valuesLock = new Object(); ArrayList axes = new ArrayList(); @@ -134,16 +107,19 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { } - private void initSensorManager() { - this.context = JmeAndroidSystem.getView().getContext(); - // Get instance of the WindowManager from the current Context - windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - // Get instance of the SensorManager from the current Context - sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - // Get instance of Vibrator from current Context - vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator == null) { - logger.log(Level.FINE, "Vibrator Service not found."); + public void setView(GLSurfaceView view) { + pauseSensors(); + if (sensorManager != null) { + sensorManager.unregisterListener(this); + } + if (view == null) { + windowManager = null; + sensorManager = null; + } else { + // Get instance of the WindowManager from the current Context + windowManager = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE); + // Get instance of the SensorManager from the current Context + sensorManager = (SensorManager) view.getContext().getSystemService(Context.SENSOR_SERVICE); } } @@ -222,9 +198,6 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { unRegisterListener(entry.getKey()); } } - if (vibrator != null && vibratorActive) { - vibrator.cancel(); - } } /** @@ -400,10 +373,8 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { if (!sensorData.haveData) { sensorData.haveData = true; } else { - synchronized (eventQueue){ - if (axis.isChanged()) { - eventQueue.add(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); - } + if (axis.isChanged()) { + joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); } } } @@ -428,47 +399,14 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { // Start of JoyInput methods - public void setJoyRumble(int joyId, float amount) { - // convert amount to pulses since Android doesn't allow intensity - if (vibrator != null) { - final long rumbleOnDur = (long)(amount * maxRumbleTime); // ms to pulse vibration on - final long rumbleOffDur = maxRumbleTime - rumbleOnDur; // ms to delay between pulses - final long[] rumblePattern = { - 0, // start immediately - rumbleOnDur, // time to leave vibration on - rumbleOffDur // time to delay between vibrations - }; - final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from - - logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", - new Object[]{amount, rumbleOnDur, rumbleOffDur}); - - if (rumbleOnDur > 0) { - vibrator.vibrate(rumblePattern, rumbleRepeatFrom); - vibratorActive = true; - } else { - vibrator.cancel(); - vibratorActive = false; - } - } - - } - - public Joystick[] loadJoysticks(InputManager inputManager) { - this.inputManager = inputManager; - - initSensorManager(); - + public Joystick loadJoystick(int joyId, InputManager inputManager) { SensorData sensorData; - List list = new ArrayList(); - AndroidJoystick joystick; AndroidJoystickAxis axis; - joystick = new AndroidJoystick(inputManager, - this, - list.size(), + AndroidJoystick joystick = new AndroidJoystick(inputManager, + joyHandler, + joyId, "AndroidSensorsJoystick"); - list.add(joystick); List availSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); for (Sensor sensor: availSensors) { @@ -555,14 +493,8 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { // } - joysticks = list.toArray( new AndroidJoystick[list.size()] ); loaded = true; - return joysticks; - } - - public void initialize() { - initialized = true; - loaded = false; + return joystick; } public void update() { @@ -570,15 +502,6 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { return; } updateOrientation(); - synchronized (eventQueue){ - // flush events to listener - if (listener != null && eventQueue.size() > 0) { - for (int i = 0; i < eventQueue.size(); i++){ - listener.onJoyAxisEvent(eventQueue.get(i)); - } - eventQueue.clear(); - } - } } public void destroy() { @@ -588,39 +511,27 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { sensorManager.unregisterListener(this); } sensors.clear(); - eventQueue.clear(); - initialized = false; loaded = false; - joysticks = null; sensorManager = null; - vibrator = null; - context = null; - } - - public boolean isInitialized() { - return initialized; - } - - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - public long getInputTimeNanos() { - return System.nanoTime(); } - // End of JoyInput methods - // Start of Android SensorEventListener methods + @Override public void onSensorChanged(SensorEvent se) { - if (!initialized || !loaded) { + if (!loaded) { return; } + logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}", + new Object[]{se.sensor.getName(), se.accuracy, se.values}); int sensorType = se.sensor.getType(); SensorData sensorData = sensors.get(sensorType); + if (sensorData != null) { + logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}", + new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE}); + } if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) { if (sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { @@ -641,10 +552,11 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { if (!sensorData.haveData) { sensorData.haveData = true; } else { - synchronized (eventQueue){ - if (axis.isChanged()) { - eventQueue.add(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); - } + if (axis.isChanged()) { + JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue()); + logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event); + joyHandler.addEvent(event); +// joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); } } } @@ -658,6 +570,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { } } + @Override public void onAccuracyChanged(Sensor sensor, int i) { int sensorType = sensor.getType(); SensorData sensorData = sensors.get(sensorType); @@ -697,7 +610,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { AndroidJoystickAxis axis; axis = new AndroidJoystickAxis( - inputManager, // InputManager (InputManager) + getInputManager(), // InputManager (InputManager) this, // parent Joystick (Joystick) axisNum, // Axis Index (int) axisName, // Axis Name (String) @@ -758,10 +671,12 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { this.maxRawValue = maxRawValue; } + @Override public float getMaxRawValue() { return maxRawValue; } + @Override public void setMaxRawValue(float maxRawValue) { this.maxRawValue = maxRawValue; } @@ -787,6 +702,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { return hasChanged; } + @Override public void calibrateCenter() { zeroRawValue = lastRawValue; logger.log(Level.FINE, "Calibrating axis {0} to {1}", 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 cd849b1dd..5ef866188 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 @@ -46,17 +46,15 @@ import android.view.ViewGroup.LayoutParams; import android.widget.EditText; import android.widget.FrameLayout; import com.jme3.input.*; -import com.jme3.input.android.AndroidSensorJoyInput; import com.jme3.input.android.AndroidInputHandler; +import com.jme3.input.android.AndroidJoyInputHandler; import com.jme3.input.controls.SoftTextDialogInputListener; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; import com.jme3.renderer.android.AndroidGL; import com.jme3.renderer.opengl.GL; -import com.jme3.renderer.opengl.GLDebugES; import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLRenderer; -import com.jme3.renderer.opengl.GLTracer; import com.jme3.system.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -77,9 +75,9 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInputHandler androidInput; + protected AndroidJoyInputHandler androidJoyInput = null; protected long minFrameDuration = 0; // No FPS cap protected long lastUpdateTime = 0; - protected JoyInput androidSensorJoyInput = null; public OGLESContext() { } @@ -119,6 +117,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex androidInput.setView(view); androidInput.loadSettings(settings); + if (androidJoyInput == null) { + androidJoyInput = new AndroidJoyInputHandler(); + } + androidJoyInput.setView(view); + androidJoyInput.loadSettings(settings); + // setEGLContextClientVersion must be set before calling setRenderer // this means it cannot be set in AndroidConfigChooser (too late) view.setEGLContextClientVersion(2); @@ -231,6 +235,9 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex if (androidInput != null) { androidInput.loadSettings(settings); } + if (androidJoyInput != null) { + androidJoyInput.loadSettings(settings); + } if (settings.getFrameRate() > 0) { minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms @@ -267,10 +274,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex @Override public JoyInput getJoyInput() { - if (androidSensorJoyInput == null) { - androidSensorJoyInput = new AndroidSensorJoyInput(); - } - return androidSensorJoyInput; + return androidJoyInput; } @Override From 73c3f39e460fa5ca1e5909430fce540586ec48ae Mon Sep 17 00:00:00 2001 From: Alrik Date: Wed, 8 Apr 2015 12:58:25 +0200 Subject: [PATCH 059/225] - add test case --- .../post/TestBloomAlphaThreshold.java | 178 ++++++++++++++++++ .../src/main/resources/Textures/glass.dds | Bin 0 -> 349680 bytes 2 files changed, 178 insertions(+) create mode 100644 jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java create mode 100644 jme3-testdata/src/main/resources/Textures/glass.dds diff --git a/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java new file mode 100644 index 000000000..7e8608882 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.BloomFilter.GlowMode; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.scene.shape.Box; +import com.jme3.util.SkyFactory; + +public class TestBloomAlphaThreshold extends SimpleApplication +{ + + float angle; + Spatial lightMdl; + Spatial teapot; + Geometry frustumMdl; + WireFrustum frustum; + boolean active = true; + FilterPostProcessor fpp; + + public static void main(String[] args) + { + TestBloomAlphaThreshold app = new TestBloomAlphaThreshold(); + app.start(); + } + + @Override + public void simpleInitApp() + { + // put the camera in a bad position + cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10)); + cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); + // cam.setFrustumFar(1000); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + + mat.setFloat("Shininess", 15f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); + mat.setColor("GlowColor", ColorRGBA.Green); + + Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + matSoil.setFloat("Shininess", 15f); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Gray); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Gray); + + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalTranslation(0, 0, 10); + + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + teapot.setLocalScale(10.0f); + rootNode.attachChild(teapot); + + Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700)); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(soil); + + Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/glass.dds")); + matBox.setFloat("AlphaDiscardThreshold", 0.5f); + + Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2)); + box.setMaterial(matBox); + // box.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(box); + + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + + // load sky + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false); + sky.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(sky); + + fpp = new FilterPostProcessor(assetManager); + int numSamples = getContext().getSettings().getSamples(); + if (numSamples > 0) + { + fpp.setNumSamples(numSamples); + } + + BloomFilter bloom = new BloomFilter(GlowMode.Objects); + bloom.setDownSamplingFactor(2); + bloom.setBlurScale(1.37f); + bloom.setExposurePower(3.30f); + bloom.setExposureCutOff(0.2f); + bloom.setBloomIntensity(2.45f); + BloomUI ui = new BloomUI(inputManager, bloom); + + viewPort.addProcessor(fpp); + fpp.addFilter(bloom); + initInputs(); + + } + + private void initInputs() + { + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + + ActionListener acl = new ActionListener() + { + + @Override + public void onAction(String name, boolean keyPressed, float tpf) + { + if (name.equals("toggle") && keyPressed) + { + if (active) + { + active = false; + viewPort.removeProcessor(fpp); + } + else + { + active = true; + viewPort.addProcessor(fpp); + } + } + } + }; + + inputManager.addListener(acl, "toggle"); + + } + +} diff --git a/jme3-testdata/src/main/resources/Textures/glass.dds b/jme3-testdata/src/main/resources/Textures/glass.dds new file mode 100644 index 0000000000000000000000000000000000000000..5344df36434ade40191ce74ea6d7c11a02f67a35 GIT binary patch literal 349680 zcmeHwZ;V{mb>C2uQH8E8)vy2tc3}cx8=I|E*su}n**NDL-$|AEwJ{Twp%PwM5MGegOLkk0JCCaIa zab#&<&$;j2d(Sy4KhbKn{?6@3TdkYm zKk%Q{E%>h%{GUdju=~ZGm*JBNx4Rpxz2VnC_9AURHs0QC@0@~+v9NJW~Ew z{-^ohjn4P7f6D)<`9`#-{IC3JdQgbp!~1=&+Sdr=dtG>l>e3gmH(ChtMv~${~wp{<@}@azw*EGzs~>L z^#`%XJLUazkALd;zmEUY_$Tc%HqLGT%Kys$)c$q+KW+Z4>;Lkue^&li{-^OTHs8zp zEC1)+Z=mb{5|8)G`2-#R*YSTH|0m=B#vfq|jNShi375A&J}6)ZOuKzY+1xWH1_kWM zYqwkJ@CIwIDB78^e4f-8G6Ew1qKJ-#yi@l#cYEC4LEb_hiiJHcSUytz?H%4;bS(Rm z+5b}8o0NZUe~cdQsD5uLiZ4F(E9{ao|U`eQXeXhQVwblY7Z!Z#rs2lxCtL`Up)R{e4_C% z20jTN-@e+rkwC>qi`RE4-=cN;nOoqmD7HFu|Nptix7O(S6RB}DzOTj;-2IjRjUmLc zFZJij&nml9d8j;40?X%b%KyePBlGRD`M>hN*8j^HkBm3Amd#)Ec}l%3AAhO67zxbG zU&{aU$?y8KKjnYrf3#eU=hvtIYCclxP3?DH`&Iro5}Y}InpggLey{wm{EwEW@iUMA zDSwx8SNqFtf6D(x1IqtK45{b)WqWRZUe4Dq8teSO(YMCm-0@f2HxgK}_`5v$pYs1I z+Mm|{TK}WnEB_ns*YyUNZ2u$rH~alaJ{%w8_CN1`pnd$f_*=qwxR3s~>F;lF>Ufmg{>JhDR_ER| zkcT^eCpDJm!$rdt{}*EafBO8tH@plVZ+~C9#`oLC#=Xqv55(VJ%;i~c58wZ7RmUtgp9|6hOj z&QD{!v^a*RJ|{a>#C z)A4_;|0(}!{U1BumG_tRS8TqQ_gDT`{#X9Db#LnNe%YQIpO@qHi^e+sZvwH7|MT&8 z<$uY~v9a?1YOnu^ZC|1LOZ(IMALl1?y1|LNBI$a-|u{a?x>HJ16O*8e%{e|Y=x4s1Sj;}6mB za*h9!{jNQ6{y=;T`9Entz5ju|k@k)MCB?x1ti9CdKS2ANo_~zK$^PT`81(PLb?E-TyMFKf4)8yw0Ope#N8|gdKkoj@|3<%7&o=%l zPUC-G-pc>V|6p?~od44GkIMgP;*avb-T$Zczp=QLjX!DpZG3!J{#X82{!^1q2ViS@7iul$d8r~GfcFJt^w{#X778%!Mkm#01De-nWc>tFd_`5*00 z`QLb7#`vrJulx@-m^l6~PkYM$CITha|FZJ`{_fc>Za%QQ6b~0?-*7(~?0;qL$Hivij=|)=0{E2gzX=No^AVo^=2-s+@82Qzm?hpk;^p5S{@427$3JP0sd0MySN>Q2_x3OO@kYnW|Fqsz{!eZ1I{&Zp|33f1@euBh z@v-v1^1t$b8TWJZpVt5J{>uM%ej5GLt_#!kf2r${RclY{ zf6j+JQGcYy%Kua!srfi{d&>XPztY>k*8e`=*xzk;fdSqA!)Vy8|6BF<|Cs%zWdC!T z{ilAsqc?>2Zi^XE!R{A#X#3HykV_BKer70 zpL_fr;y>Yk6K@Is#KwK5e>?8!?ej(-kIOy&mV5ta?)cZsGXG-vzYzR?Z)bfC@==qL zqUEQ?(fGdFKj`kS{BHsq)|>SG+sglaHvTU8IX2Ew|119=9IOuC%X~)pKX3d~{-^nd z<7dg&i^j_T%Ksn%E!|ZL)wqsUwMBc|I1V#@C~xx+{B}(Snj{){%8Dw_}`*&58wYt`k(&%5m^6}eE+@I zx8eWf_P4C-|IS?4K>mkM=_k>!|1kFcI(h#h z`}wD-<*n@*31q9crTkaj{#Ud9_WUpHAq`gkx9k6v|MSMfRgZsZ{5^NP(e_I{+q%Zf zW7X|n`9DYeRsL80N4u-XqcTqU_!Jwjdi;wmU%kJPz|z$FQXi{s|H}V4>{Iz)`5*00 z`M;m#{LOUvmE;@6MgmKd4=DfVJ%1$6`n%lzul%q4k8x1>U-^Hz_|QB}^7T;p;<56- z@;}&o;(S#3U-=&+t@6L}zhT_O@h5e>Ti*7R|CRrh|1A$iTkkLT6X^PX6ZWm#^To$i zkAFrD$@Bkw`qus$3AlV~)$PB>_Wz`R|Co;dFVp!y+q-7}pWFW*4LklnJ8J(QiO2Ex zBj?_K4Do2w^FjLa4hiB^ltD^r=_gjO!$^FfKXVQN9{m&zwM?PO{ ze9Ft;up{xm8yy?|C;d%-{sg>#hP0nr-iH54`#o{~MtltY-}LsEKK_BkPx|)L$ba1U z|Cc-W;ro9b|E9*ginD_oy;bY|^ z&j(0>Y5VExA9)^PYP_2Dm$kS2<8P}L?__`T$G;rszl`Jmt}mNd{5_u|E2bKZI9+_>*Zf60E__~#$wZV&kX9v}bB`2$4%@iDZ2K-&jzAogJP zKgY%=Mf*hmN%mip_EPhc@wdyq|4ICv*q3SVly5(E|5v>|!~ewo(#JdCf6{&$eFXOZ zx&8mu{=Z~6_4@xo!T0~?SpUuIUBfpl-p9S(ALhtEmnYukKY#21K2{#7?GN``!1*UD z%s=M*9moGF|1|t#?cMG(S6s?Hl{`HEZ*}PTe|L4>`w;SteU8*v`Cs|pc!FDx9y=Z> z|11ArR{l@sZ?XMZ?>|laRQ@mD|D*N4*8jQRKce+NM67K5qxJtZe34H-%I7ikqx`S@ zk9LLQzdHW!>%a8p@AZbt|6qv9|0c}aao2qEFprT@AKJc=KxBVV{#X7-J5&DG@qZov z_xYdB|11Bd;NRT&o$|l(zw$rkKg$2g|H}Wl`AFL@^R86mKg$2g|H}W$|2Y1m{IC44 z{F|14bIW^q`F~@_Y(H@C>x+b|_y6hoKb`-F^?#e*ACv62XZt&0{-5l>?~C`J$HqNe z|8bf2A0i>ypWYiLz!~=+^7CJEjQ@lDi9ML}08(ROeHfoL*zx~H_YU}2dr$5Ex!cqFZ`Id-=b!(`JO04=ALoNG z{@8es9-H%LNc-{eYOnvzo!{i~Kg<99A?N>lJL_wB8lRnqNR6ZMeKnro?yvlB3}5+Q z`9JsmN9BK-Z^Y+&p5Ltc_@DAWt^ZT=y=>3%v*hbVW95J4e~j{ieUh&I13 zwiA}7rP$KUw=w}NR5^MmH#){`cFPS-b=h5fcroCCx4#BW95H{Im-Vg z@XGP{)L8kS`?vCc?0i7^U-@78KOg_a_U~oCzt;a+|NHsB*nBVVkM%{~@z>n`sr;|} zul%q4U&bN3?meA6l>aIJZ|}BuaPxuHLp1Ex|D9#~|Bw%{eXsu;9|QlB{^sW6)a@Jo zC;d;a@4WKQ6Mwva^)lz5`QQJJ+YP-u%=lkY4CDW#z104<>h0z6zj5pT&UNm^{eSNL zzp3$=-CzF(&Uab-}-n?Nj@2B#>=>Ft+^j#2@cpx%oe~eD(e} zw*Snl-#q^3`G2cJ_y671dGAA%e^cXVd|&M!boW>OH-@YE&b{89cd?MD6Y zdiS4spFfZPDgT%I|Fr&}*M9Z)MgrIE_&}ccqx@g`jq?Ah$B#Vzr~I${kDjOTN8`_G zum4+h{cHb?1hVDt%Kvn}!Hv($@%lw$9sf6Gul&F2@h?yO(fYr%JM};Hza06u*8f`n zqvvV-(fE@i{wV*KcBlTQ{+Gl5l>e3g(epI^X#B|$f0X}AyHo#D|I6Wj%gX;3cQ?8) zAJDdb5e>WX|824Vf7R?U&-?-;`^I9U#E5YnOoqmKji!Ww&4B0z`w38 zOpVW6*ue9D@Da@?8g~58-~VIV_4b(CKV!GA^S?&I+1CH`SN#2hvF%&$KiR%>+h1>Z z6+S-Re(4&n2TO-n^ZW^0{}%=4|Gmez@cbXgzo~IFzL)ug^1lfSMWOsZIX=ebkKFNh z)z5#9t>0z8e{TCz{?8GAl>hDgzt;ab=AV3iznb~C?|<(2tNfoM{(AlA&JS|ipYneW z`&0f`{ztph`hV5qukwG6_^bS1`W?m}9sdVeB%Xg%{#X7-4^;jy<4|h<^X=uXzm)%t z1tgBYUjM6Zf6D(k>`(b$`5*00`G3{pukwG6_^bS1`kmJQdE$@qzw$p?p7MVl|MT%* z>wlwxm94*5-Tt)x&td<|%KzKDZQTFo-v1j7yY>HPSAPFLtpD5ed}Q{YlKnUFG3@^) z`+t(`_m%sL4}Aa4erVGF*tj>`+}vdShwV@7`C?;u|2ykH-+yJ^N6+N3=3?i_ zkG%g$um9A1+(-Sh^B=grjr@r{EFMGqr2lSG#&Y!E^|GrrN)f-Cr#Krv~*_OjvJ`<=SSmvd4J`9V|YcO{IC3ih=Wv|D?9J zvOVR0<$tsU^e5&2ocyQb|9t;;Ihj zkB(Sx`{R91Z@xapX~oK^#>V$ ziH*(tKWWeOKPiU(llEfwcjWyysXkKMAJl(WY5S@DANY^O8|Z&h4E<;AW$6Ebm%p(O z@;vb|^#4iUev)`k`j@A@as0p4xpxiqZ}~ShZUf)b{eaPM#sB?bd*=@Lc>S;1`=@37 zVf>BoV|={Y>%YL;@RMx5r)+L{+LQLz6Z60EF}J_e{=e$&^@eo(E%tsRdH?+TkMii> z@_&E0zIG3MEZ@KW@SUH={5uzp#`o3!e|LZ7e?tVg{y+El8;`&7<2|o0bFY6|Ha<@M zJktJB>p%DYi#+k)=YP4|SN`YmD2+ap|I79NTK~)V+ZXj$e60KroU8SJm5(a_=imcx ze`)x~x2OD{L*L5(%KsQ}wf@)o-w8|`Cs{8`M=D6R&oE2 zkN1ZCGmbwf|E~hyczcuih1UN$^soG{{IC44{BPI!`1qVUA5i|!5&yluB_F58%Ktg^ zul%q4ul%q4ul%p;|8nq;&p(v^bI5;L`G2F_|L67}M8nnk|3T62^8J75_g|knbt?Dx z>t*kcX1yQTpBf*-`oB}6eIgIjepC$O|C_XZ$cxzi#{`(1Kajfq&fT7=|D);`;_HO_ z8~!KyPk;Ud>~|*mPal8GW^cFtBYpew{0aU%{b5|3|NIT!zclh6$N#wBAD_q81*!3w z3ma_ze>CjQ|Je}ypF19=Za??=fBhlkKZO6&_doakM_qpaI(Ph&TmPxoAL{y3qw#F| zPi=3@(Vpf1qS)%t{r~bj{?s@c-&gzp-TjsSjUnRt|Ge-2Q~p=}2UE-z|8uWDQ2sZ8 zD4YJ5gO5|&zw&=M|6deZ|11A*lJQ?N-@wNI`TX4CuPgs6|EG@s+Fr@~cAR_F^}nqD7SHdY{6YC&`G3{;NBf&M|4{x{{#X7l^BoP2`^%N7wg6`x+Fyzo~H{+E>i=5nmp8 z?Jpt!JafxwtpCjZKgYMY-=FetYW(b1o_hk$AHX9B%qKQ}?kT?iajUbwhU>L#`zbK4 z|FZ3!dwdJeUvT}8jkh}IlJ{527vKM9`;YRb{ZE1M`~lm3YwiD~ z7s}USUzhcHY^?Ra*8f`nd;d={-cQGOu{|pP*nBVVul%q4ul(=*KQ_OI?jM`)<^7fa zmH#RK$nmk*SjYdB|LJ&JC_dh3*LD1#+kfnMBk!-{|2qCp;Gu}CF}Fp zSnGeS|F!;HBAKh6JBk1uHbuk}B*KRG@h8|(PL zj{np8Q;v_t#ybAb<6rE0U*2Eqf35$u{$HN@e|vYkOVl8G zzd!###-lJ+|EKn+{NF5plkvPc|IMEdsQ)jW|GV?ZD*LG819Lps@nb#6K-=v*icQ4N zoOt0C@W6JvH8Z}_=D%-Hnaa|u=f1&G5?GNn>^Fx2XI=&v?|GU{b4d;hGb4yXYdV$ve=Q>**I)2g= zPd#3>K>x?_H?JR~P4x$j2UEs_zTk_xf8XH#zdO^fN79Jr~y9!mabiZTD(^!ba)N9TC{d4J!u z|I&YC{nZpVZQtB~PWf#zd1Jay^%265|C8@Geg0bg(a?UR{Y}@t&;Ktn{>Sx9a0lnV zO)>gA=1bK7N8-x=alC`p|IO=n`TnmjUBmXg{zAAnoUeZ956{ELx7R#>q|X}~?+9H# zfcS~^n}43z{pbEYC=OY@9Ksmy=c}J{>wh|b^ZJnAm+`Z?{mddi@BjPE|M5H@>aR_4 zXuXWPKGy%Gd_p+4{?-4DhN|%f>F-6+WAUJl)&HsdFdkAqmUSXY-xMD#8UNS)UB(0T{~O8wtNb6{OJ;i28jGX(e-HCtlK+P>_7Yi=F>(i~3*c zSFw@6*!jG={WZ-Was8Z+KcVpgejW1-SU>Wl{rvziZ9jz3U*;Qs$!#Az{)GA~ujlH& z_y1Q}{r|1bxen^j`fpSG@!el$`(?)B%Kve_6d(V~t+F~FFY*iJ<55`sU;W?O;k^Ej z`}rXGclpCue8c#^J!;+`=j!*Scz^e+Z2$XMT>1Zxuk7z$-}yiH zZ>i7bnDe!)7sL3F@qN?wN96~h_%X};llQlI{bxRTVSncve^{*kXOTb6-wvQZ;Jm^0 zAJ+f-!|g6z|JQWBUw(hAzgPLcyS^g+ADaKF`{#W2EbrH}{aNf!{omIQIpiPH|C`SL zJO7scF0uN*NhGDc3`D)&9CQ9x|DPOBG2X@G{}l3HF8|-?f?>P*VIR)78i_0a{~xgb zJ#YSR@;5gB4$s@L{+xaOJGcL%{TBy&do&-1e4mVm8~>Sz`TR4EU(osdFy7q!_e|bk zJV`O%Kf?3*dOaSx9+sDsZ&AGUl8`^i-_89dV(|Y1|NH62Z%F>n=KrdE-km=u^ZO8% z=kJZhmH)p0=X2!se_nqU1)txJjSs;8=9xdQ=Ld(Z{txjF`v1K7yGdlmtpBaYH~xI` zIzA7*A5^mC$IWjn8gqY^&lAFnkRP63>HW8<{mr(2#rl6y@b&-q!u}r|Kd^FdibLmZ zy6fuy#*h!Ld;g#8KcV;|{cG9$KlJ?S|2$qe|E}WpSgii9{tpsR|DQMiTrU1E^L?K` zjK$XfUy=F0%>Qchon>Tk`QV|zP4h!3|LN){pRe)nBcDU>+xB`0BfrS`Z}|Px_3tSE zk@aN=^ZLE1{5bz3ABNhK*Dqb7`{$+pL(hZXKTh&z^Lr*gxI+NXK7Ld3|9t09K>AOf zkJ}U42e*eh2KzZ_?ak)xAsj3Jhy6Zm{(mg4{Qr_T|F5ziA`fFv6Y+fYYskJr*LnOH zl=knQH-xdj^Nl};p6^D#zG(T&_!GuC>i3X;hpxx+|EA}EssF40n+WCdxu)@I(f&VQ z`_0FTXUpG1{vEom{$J{O+I(reJagM$Q+vJ9zMotEy!}=8e>lw?*8ltcgJbdJ{D0zn zv#cNIt6yvRofuCDjX!Yx%X&P7%l!Y6s}~T1hhE1xb$l`MXZFJDWiuYb z;QF7YUgz!0@t@jwO28gx8~+-||6lIh$M*j>o&S^XgBgn}|Ifewx8~ns{yaSX0P%>~ z*E~2hUJ$x|0PzFti>w!EI-lJ1`+4$L?|-S|&7#|@`Fmsgn`QiKod4r_pJ+ceFK&v* z`gpQn&Rg3*O>nM_%qY*H|_63{vEnr`G2GJ2axQM z`lUX<9J)SV{Veq}lm1Uy`Psbn`oXe(Z*D)cjDH34|39;Gtnz<%o<|6~{9fk!61(v~ z?*AeC4qrcH{5{S1+bsFh;_Qo$A65Q4b^VUtpJ)F-0l&|?{($#CtY70FBwpoye#z(8 z2cG2rSdUWwZ-ST2|KqRUo>$-8{+rGpssF408w+e$?@a6ecXi(T5cU5MR{w7r57htZ z`Z?wQruH-2_apnidw+Np59)ZS@_#yiH=8_XuLrpQBcDUP-Kc(t{GH{)yk4JX{(HXl z>mV`GepC7J{x|i1S>M(7>(|ED#CWf)zneaP?(duN6E}WTfci^rj z?H#N??fk)5JjVYqejl>==y3howBD89pKtzJ=3`B<;a`{kV*lmyh46gOzg+xZ@i&tP zrt>17Unrhs{@g!LQ++S``?Kkf@ITA{<@*0E@jk#1zPP*1_WzH?mH*ez|L66fjU&q= z??vk`_x)zGAFq#I=JAu<^2^=+Z2I&5&(HtE^*xv$S-)$F&+PvCH|X!j;<5gpTb`@d z{%rQ6{$JXO^%F0T)b=x*{-*W+(0sqVKGxr>`rlnw|2Kwmz1okrcjbSh$cF20y!_^k z7t7`U7va2rlRi}bJ{DL0e`B2gKhOO4y!s!Ml6LktdcN1&Tm5>D^XL5Y%-jEYe?Pzc z;0f4#<9xVjf29BS#Q4!@9N>Q#mo1;8%KxXvc-|kp{=wA`V{zsGm#%Dx{Xb3nKkxUY z{HB{f@%EdW|M>F{#Qb*%<9J)%^$+vvZ{FX}CqF6Q^z)~)_22q`QM}5=|M304Fy0KG z^1e-R+6q3*W3R2>er$EBJyk6|JlAjb^agV zdzlZMh53J4Ka9nd|1ZJ$e{-$BmHst<{nvc`8h!t3j`nfB*V?!AhY&`8op1e}@qZU@ zLhZ@x^S3YKS#$l(*Z<}6|LxtgJ7^C@HGVJ_tN$Ct)%VXdx1VM9|IqsVMf+dfzk4FT zk$CR-Q_II_JjDKmu=@WUcUbP z2Yqka_N9EL;s1ZKcR=98d-8K7vod1u-mH)r+N_&T%|9JrN$MFMePvLrY-ud^z z-1!^Tr?j_e>_12T=jGcpUdr!t`8Dl-{rgSVU*!Eh+Uq>$FAm`M)P9=V|7`uY{tx^A z+5W%vwR_q6|E74XzgOb{?z;8=@8kr@gX^c<6$mSEm&cCIvagH>^k^A#bu;)>j7)|iMXEo z{5sD6m3(M_F#nl=O+K{g{jd4>g|vs%SdQQD_Dwy@#uMoEQTZahK8rt0^Lx+#a{uqW z?EQbC{r>VguJ@<;!dP7Se|`PGw5Kxf@bezr<+opno!c)%5vl{w?YS z{ydd`F?(9&`~l^0efPhqy)5SY*8g?=zxuziD3@<6#=fSH*F4^j;zQ&Uf4>`T{3Nx! z%l4-B|Ay=TuJ`r-lHWrZ`A7L5Y-;BCq5f|?a%TC>7C&P7|3>%y&%+bB^Zv)-&4c1+ zA1Z!!>e4HJ{gqRvPW=d;=h+jdZ-RXTukPaSJ>C!Ci{iTceF*d44+@ES`-5UK#`)Cw zo}ag0mxt_+{C?B+m+Sk3Vw=gYDK5nCo9m;gJoxk0+i!ZkY5Vf~(jKI}RQO>3UwmNy z$nm21i>EJNzWiiSjn8j&9>e*67(#%5!>1{J?x|;=qV<0m-~LDUz{j_@zV;H_fMR;T zQRk6i{b#>_PV8R};jPZOwe#??zaMq~Q2X*a2#?4w{62NeA8h-f=N)zZ^YF31Z~pxE zt>OAX`+J=4#iwtA>GMA(-X9;rIDT{uKDNF4MElnRrt-gc?XCxIBKC`zo`*3K6Z>sC z-;VO5@*ia%RG;!X+6(yq;He)`2!YDBo6ui{cFF))}t&N#`r|#kN!>V^0k$FSZ7?yK0!r!J2|$g6+= zxNv!$Zaw@P`<>q?ic43_`0Vh=?JfBG{-ax+!!u{jSpDzecq#%`r{Muhx-0A_}KZv`h5QYEsMlt{*U7as4t5h zzl_y?e|Qso{PPbAy8re7`h$_jUzhv)o^Y%YvPqIBO50n??PgGv$E=2#YQ2##%Ocy@LR2x9IvkQkNiHy|0nBz z)OuGCAJeC4*!i2$Ke2z)KZ!SO|D?VpuJrHDf5YeD?Hhf|=W$r-Ut*Lue}1n28T#Ka z{?z3&Dt=7TKd9g3&p7@F?&|#i`7+-tto|`yGW_r2ztn$o`{nYJ@@$NS{ObOJ`MI^9 z%066tl6Vk^FLnLT+kY8<`16PDZ*==a|BU~C0Q`T3`2V*$=QI9$^X_-|H#E?@sprruvrO=kk&EUdLP>b$M}p$=??v z?BnGT{Auk$+FQN7X3vA|8woi6Yu5jf&kqW;&wBv(2W1vk|NY?$jrjlK?(?Jc|NO0E zR!%|;$+)Uo`_m%ue{)+h%-`@m1aL*HdzGlxO?WvAYe}}An8lS4? zKeYO;u;VkEe@xOp`2XA1-(~*i#_L@E*ADQ%)VIVuKRT%F(>{MwJP`g{$K3x5_&$!m z+4exT6XU0}7m4wHriaddWq&1}?B94lUSHIoZL2QV=Kq-Qy$v6$fBAm+O8+kZ zgYm0?{-r!7Kfkq4ua6$aD`)QqAPu79KK3CwfPKcBgu`##Q`d<`+2W({mbVarEmWHljCEhUuXaI{>uK8 zcvk)6ds&W`J?(h4#~6ryfnUr2D-&pz|z<4T5|GD^I*T-Sa zpC{+H_5N}H;PEl!&-Lfw@u3Lj5Au1qzU%j|_m@9^Q+>qP%-{BQ5k^);yf z-1xs-Z|vs($M}C!{fGHa_E+M``mg-i(f#xK{|}?af86|^9RIlp`9IpPt^Ym?>#^n=|CjzGaj1X&Ks=TA zlX$dzrq{oW*Yf@$9Qu8z|Do%n`{(t~$N#?-;Qxy-{})&PB|mxjODyFT>c6y)y1&jc ze&(Mq)PCrC==sd^y85Q7mm1|Kbo`{shur+1jQ^qh56Az^en&U{U#Eb|qKEBUbX36sbFM$3O{ z{fqr+ZvFX~`mX@{-Nx9|O%%d@VZ$@Y)(py#t<`^ZoJ{(uAPdxF0w_mld-_W1aP zv_ILu!`kyr_Fu^lBltFV_F7LkJKWSeQ*V{85;_OL&U)tv( z^I!Qq(mofB2O_>nJZ*bc|1$pzt^W<_ANBptJMR$xulpyLcU>RgZ^VA<_5k)Lub=e( zUB4f1PwIDceDwF@`Y%lU@#}A<)c?f!1>T=}e4^K-zGeF;fBOA#c#{6<`kybu`4MUW+f_m9Bge(=HZqnB|0Xz1}t2p|^Y{<4>G zKUsDC`|y37U9cE`KM9-tNaXtlgM-=M*YD5U!|{#D_lNf*k>|tl@Rxt|qaWG(?;$4b zOKiR`f4@MyzINvt?#{H&V=(DI@;7}xe4eA^I+4%cMSY?Cuj2ixd=K}#8#o5b^B$c3 ztM8*e=>BUte~9Oq(EH>0Ojw`W_T~3SVgCET`3le;)_3FY&(Gt*^{({(h%tWK--pgS z3(0Sm>-72M`9q=eg^<7L^Vj9S&g4J#`TieTOY3+w|9kMg|AtcfP-}Rh+VA~6y!_0G zS1&+@i2J82JiJit$FA_PUbTOF%=Oy$_pANb_V)t+`~trY?U(zj?e|4{^82>E?-hox z?)mv&ZgfH3o5e4%Ke!+IJ5PZTAKmW^zg!;N zAGTqAYw&qaw;uV^{onu7qWIw85eVq7z5ci7il3h=-gtbg^EQqLey`l`=lbu)d--|~ z%r|?1{d_b(lldP09_*Fm_XCV9e{A!$dcJF}yZP4ub>c&-nhIMg5<8-`M`w^0h$wBcI3h|DYW2_|o+Km-pkiF7Lj1zH$A3P5TqZPk4S`*Dt@0`83#rlTW=rJyxH~@!4_rxBc%Af6*VIe`o&~ z-;Do{)jyXv+9M=W?s+SH7G9tA{&D-S+0(Gs>$&??@*wsv+lTwJ@56ix@h9tVw*Tn= zwEvjjnEe0~{Qt;DK61qNXVmjRd+zz_@|VvW!lk|X_T=~L`ls#J<3suTY5RW+_-C^I z>-wVc4&_7fK;VPY-<^F}{rLXM`^)~z>tPJfGco>A|406m{r@I>?DN(2E892r=j=iD zSIVREUosxc`&<9MY%ho>#=q}B`dHBZ1N{%g|EcWNtzRq3_~+y|O5UhXs}K48qI??n zdmA6E{mb|#{U7r;seiiOv)&%pSN*!wkNN(le}0({$nS@+Y=1N!*MF>!Lh+CHU*6y3 zLp|Rgb6wiA>+e9sgRs0t_TTYC{eGk6Y5OnZ|23%BL;gRh|D)Q6!hzW{jfbP+9lc(+ zcO%bGe(0{FJn8*xd>PmOH_HBd{@*RA5n}AQ!K9&{4MeO ztM`@7zC z?A78wo@jmH$MAk}z^#_WgW-MvUu4(&Ll{4Ru?6}2qSZQnICKg#5D?%hIOx1B$3+n4?CjgUW=XK(nE^YnRoL+IbP zy?pvZxXk+7Y8{n)*s|^K2>B0&7lr<@fAoHmUwgxE3wc9tS^xR(@qS+aJJ-JdsL;P? zzd!uRPiXtF9%PT}cW;ljhw|{}%^=?$Jxa$r`hbhKWdDoS4{rY3&0D~AKl;RNd+qjz zf4cvk*6F_~@cAIz61>MS>W^RFV)#X{FM8eLU;RJ7`QHC^`VdC9!2W)>-F~a}%-4&5 z{k32151|77r6Wf^cm?9!{$t?(!wca4EsF;gK5RYv#@c^*{-NIRcb<6i3-{df!7Kmv z@BerB!|O-jXC-ufYDlcD{9D@M{nM3jFnkZ#<6msQo>*$A8}+{`pIr$Nl~K z!&^Vx+VsCK-#>8Jf`|W`+fMg}AAREHo8R3&F@T4M@yg!t_CJEhC-{xW5f?3Z_=A7& zOD|r-_P@~n{cqUuzR!La&o?~sof9{2wr<@V6t7;u@v Date: Wed, 8 Apr 2015 20:02:33 +0200 Subject: [PATCH 060/225] Bugfix: fixed problem with ipo curves computin during animations import. --- .../plugins/blender/animations/AnimationHelper.java | 4 ++-- .../plugins/blender/animations/BlenderAction.java | 12 ++++++------ .../plugins/blender/animations/BoneContext.java | 11 +++++++---- .../jme3/scene/plugins/blender/animations/Ipo.java | 13 +++++++++++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java index f8c1cdad3..1d889f992 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java @@ -71,7 +71,7 @@ public class AnimationHelper extends AbstractBlenderHelper { if (actions.size() > 0) { List animations = new ArrayList(); for (BlenderAction action : actions) { - SpatialTrack[] tracks = action.toTracks(node); + SpatialTrack[] tracks = action.toTracks(node, blenderContext); if (tracks != null && tracks.length > 0) { Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime()); spatialAnimation.setTracks(tracks); @@ -110,7 +110,7 @@ public class AnimationHelper extends AbstractBlenderHelper { if (actions.size() > 0) { List animations = new ArrayList(); for (BlenderAction action : actions) { - BoneTrack[] tracks = action.toTracks(skeleton); + BoneTrack[] tracks = action.toTracks(skeleton, blenderContext); if (tracks != null && tracks.length > 0) { Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime()); boneAnimation.setTracks(tracks); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java index aa36c918e..38a437ba0 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java @@ -10,9 +10,8 @@ import java.util.Map.Entry; import com.jme3.animation.BoneTrack; import com.jme3.animation.Skeleton; import com.jme3.animation.SpatialTrack; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; import com.jme3.scene.Node; +import com.jme3.scene.plugins.blender.BlenderContext; /** * An abstract representation of animation. The data stored here is mainly a @@ -69,10 +68,10 @@ public class BlenderAction implements Cloneable { * the node that will be animated * @return the spatial tracks for the node */ - public SpatialTrack[] toTracks(Node node) { + public SpatialTrack[] toTracks(Node node, BlenderContext blenderContext) { List tracks = new ArrayList(featuresTracks.size()); for (Entry entry : featuresTracks.entrySet()) { - tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true)); + tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, null, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true)); } return tracks.toArray(new SpatialTrack[tracks.size()]); } @@ -84,11 +83,12 @@ public class BlenderAction implements Cloneable { * the skeleton that will be animated * @return the bone tracks for the node */ - public BoneTrack[] toTracks(Skeleton skeleton) { + public BoneTrack[] toTracks(Skeleton skeleton, BlenderContext blenderContext) { List tracks = new ArrayList(featuresTracks.size()); for (Entry entry : featuresTracks.entrySet()) { int boneIndex = skeleton.getBoneIndex(entry.getKey()); - tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false)); + BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(boneIndex)); + tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, boneContext, boneContext.getBone().getBindPosition(), boneContext.getBone().getBindRotation(), boneContext.getBone().getBindScale(), 1, stopFrame, fps, false)); } return tracks.toArray(new BoneTrack[tracks.size()]); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java index 0eb50fb65..adc0a5c4c 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -25,10 +25,13 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; */ public class BoneContext { // the flags of the bone - public static final int SELECTED = 0x0001; - public static final int CONNECTED_TO_PARENT = 0x0010; - public static final int DEFORM = 0x1000; - + public static final int SELECTED = 0x000001; + public static final int CONNECTED_TO_PARENT = 0x000010; + public static final int DEFORM = 0x001000; + public static final int NO_LOCAL_LOCATION = 0x400000; + public static final int NO_INHERIT_SCALE = 0x008000; + public static final int NO_INHERIT_ROTATION = 0x000200; + /** * The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us). * So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results. diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java index f388b703f..f5113129f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java @@ -137,7 +137,7 @@ public class Ipo { * as jme while other features have different one (Z is UP) * @return bone track for the specified bone */ - public Track calculateTrack(int targetIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) { + public Track calculateTrack(int targetIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) { if (calculatedTrack == null) { // preparing data for track int framesAmount = stopFrame - startFrame; @@ -236,6 +236,15 @@ public class Ipo { } } translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2])); + + if(boneContext != null) { + if(boneContext.getBone().getParent() == null && boneContext.is(BoneContext.NO_LOCAL_LOCATION)) { + float temp = translations[index].z; + translations[index].z = -translations[index].y; + translations[index].y = temp; + } + } + if (queternionRotationUsed) { rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); } else { @@ -292,7 +301,7 @@ public class Ipo { } @Override - public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { + public BoneTrack calculateTrack(int boneIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); } } From cdf614d3ad49cbf0c31f0eb72338b146a620e602 Mon Sep 17 00:00:00 2001 From: Alrik Date: Thu, 9 Apr 2015 08:58:04 +0200 Subject: [PATCH 061/225] - revert commit to master --- .../post/TestBloomAlphaThreshold.java | 178 ------------------ .../src/main/resources/Textures/glass.dds | Bin 349680 -> 0 bytes 2 files changed, 178 deletions(-) delete mode 100644 jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java delete mode 100644 jme3-testdata/src/main/resources/Textures/glass.dds diff --git a/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java deleted file mode 100644 index 7e8608882..000000000 --- a/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.post; - -import com.jme3.app.SimpleApplication; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.filters.BloomFilter; -import com.jme3.post.filters.BloomFilter.GlowMode; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; -import com.jme3.scene.debug.WireFrustum; -import com.jme3.scene.shape.Box; -import com.jme3.util.SkyFactory; - -public class TestBloomAlphaThreshold extends SimpleApplication -{ - - float angle; - Spatial lightMdl; - Spatial teapot; - Geometry frustumMdl; - WireFrustum frustum; - boolean active = true; - FilterPostProcessor fpp; - - public static void main(String[] args) - { - TestBloomAlphaThreshold app = new TestBloomAlphaThreshold(); - app.start(); - } - - @Override - public void simpleInitApp() - { - // put the camera in a bad position - cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10)); - cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); - // cam.setFrustumFar(1000); - - Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - - mat.setFloat("Shininess", 15f); - mat.setBoolean("UseMaterialColors", true); - mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); - mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); - mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); - mat.setColor("GlowColor", ColorRGBA.Green); - - Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - matSoil.setFloat("Shininess", 15f); - matSoil.setBoolean("UseMaterialColors", true); - matSoil.setColor("Ambient", ColorRGBA.Gray); - matSoil.setColor("Diffuse", ColorRGBA.Black); - matSoil.setColor("Specular", ColorRGBA.Gray); - - teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); - teapot.setLocalTranslation(0, 0, 10); - - teapot.setMaterial(mat); - teapot.setShadowMode(ShadowMode.CastAndReceive); - teapot.setLocalScale(10.0f); - rootNode.attachChild(teapot); - - Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700)); - soil.setMaterial(matSoil); - soil.setShadowMode(ShadowMode.CastAndReceive); - rootNode.attachChild(soil); - - Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/glass.dds")); - matBox.setFloat("AlphaDiscardThreshold", 0.5f); - - Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2)); - box.setMaterial(matBox); - // box.setShadowMode(ShadowMode.CastAndReceive); - rootNode.attachChild(box); - - DirectionalLight light = new DirectionalLight(); - light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); - light.setColor(ColorRGBA.White.mult(1.5f)); - rootNode.addLight(light); - - // load sky - Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false); - sky.setCullHint(Spatial.CullHint.Never); - rootNode.attachChild(sky); - - fpp = new FilterPostProcessor(assetManager); - int numSamples = getContext().getSettings().getSamples(); - if (numSamples > 0) - { - fpp.setNumSamples(numSamples); - } - - BloomFilter bloom = new BloomFilter(GlowMode.Objects); - bloom.setDownSamplingFactor(2); - bloom.setBlurScale(1.37f); - bloom.setExposurePower(3.30f); - bloom.setExposureCutOff(0.2f); - bloom.setBloomIntensity(2.45f); - BloomUI ui = new BloomUI(inputManager, bloom); - - viewPort.addProcessor(fpp); - fpp.addFilter(bloom); - initInputs(); - - } - - private void initInputs() - { - inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); - - ActionListener acl = new ActionListener() - { - - @Override - public void onAction(String name, boolean keyPressed, float tpf) - { - if (name.equals("toggle") && keyPressed) - { - if (active) - { - active = false; - viewPort.removeProcessor(fpp); - } - else - { - active = true; - viewPort.addProcessor(fpp); - } - } - } - }; - - inputManager.addListener(acl, "toggle"); - - } - -} diff --git a/jme3-testdata/src/main/resources/Textures/glass.dds b/jme3-testdata/src/main/resources/Textures/glass.dds deleted file mode 100644 index 5344df36434ade40191ce74ea6d7c11a02f67a35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349680 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 Date: Sun, 12 Apr 2015 15:31:48 +0200 Subject: [PATCH 062/225] Fix issue #255 - scenecomposer : forcedCamera - Now you can't use scenecomposer tool while overiding the camera control - Fixed a tool modificaton not undo/redo able. --- .../ComposerCameraController.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/ComposerCameraController.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/ComposerCameraController.java index c16933f80..c1689b833 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/ComposerCameraController.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/ComposerCameraController.java @@ -79,26 +79,24 @@ public class ComposerCameraController extends AbstractCameraController { @Override public void checkClick(int button, boolean pressed) { - if (button == 0) { - if (isEditButtonEnabled() && !forceCameraControls) { + if (!forceCameraControls || !pressed) { // dont call toolController while forceCam but on button release (for UndoRedo) + if (button == 0) { toolController.doEditToolActivatedPrimary(new Vector2f(mouseX, mouseY), pressed, cam); } - } - if (button == 1) { - if (isEditButtonEnabled() && !forceCameraControls) { + if (button == 1) { toolController.doEditToolActivatedSecondary(new Vector2f(mouseX, mouseY), pressed, cam); } } - - } @Override protected void checkDragged(int button, boolean pressed) { - if (button == 0) { - toolController.doEditToolDraggedPrimary(new Vector2f(mouseX, mouseY), pressed, cam); - } else if (button == 1) { - toolController.doEditToolDraggedSecondary(new Vector2f(mouseX, mouseY), pressed, cam); + if (!forceCameraControls || !pressed) { + if (button == 0) { + toolController.doEditToolDraggedPrimary(new Vector2f(mouseX, mouseY), pressed, cam); + } else if (button == 1) { + toolController.doEditToolDraggedSecondary(new Vector2f(mouseX, mouseY), pressed, cam); + } } } From cd97741f9cba08a043a1834c5cbb5be5fb9ab6a2 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 12 Apr 2015 17:39:18 +0200 Subject: [PATCH 063/225] Bugfix: fix for IK constraint: rotation locks are now applied only on boned woning the constraint and not to all that define it as it was before. --- .../definitions/ConstraintDefinitionIK.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index 1d6139e04..af1367e4a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -88,14 +88,17 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { if (angle != 0) { Vector3d cross = currentDir.crossLocal(target).normalizeLocal(); q.fromAngleAxis(angle, cross); - if (boneContext.isLockX()) { - q.set(0, q.getY(), q.getZ(), q.getW()); - } - if (boneContext.isLockY()) { - q.set(q.getX(), 0, q.getZ(), q.getW()); - } - if (boneContext.isLockZ()) { - q.set(q.getX(), q.getY(), 0, q.getW()); + + if(bone.equals(this.getOwner())) { + if (boneContext.isLockX()) { + q.set(0, q.getY(), q.getZ(), q.getW()); + } + if (boneContext.isLockY()) { + q.set(q.getX(), 0, q.getZ(), q.getW()); + } + if (boneContext.isLockZ()) { + q.set(q.getX(), q.getY(), 0, q.getW()); + } } boneTransform.getRotation().set(q.multLocal(boneTransform.getRotation())); @@ -124,14 +127,16 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { Vector3d cross = currentDir.crossLocal(target).normalizeLocal(); q.fromAngleAxis(angle, cross); - if (boneContext.isLockX()) { - q.set(0, q.getY(), q.getZ(), q.getW()); - } - if (boneContext.isLockY()) { - q.set(q.getX(), 0, q.getZ(), q.getW()); - } - if (boneContext.isLockZ()) { - q.set(q.getX(), q.getY(), 0, q.getW()); + if(bone.equals(this.getOwner())) { + if (boneContext.isLockX()) { + q.set(0, q.getY(), q.getZ(), q.getW()); + } + if (boneContext.isLockY()) { + q.set(q.getX(), 0, q.getZ(), q.getW()); + } + if (boneContext.isLockZ()) { + q.set(q.getX(), q.getY(), 0, q.getW()); + } } boneWorldTransform.getRotation().set(q.multLocal(boneWorldTransform.getRotation())); From d884ecb317a15cea5a765a9e4eecdec009368a93 Mon Sep 17 00:00:00 2001 From: Erlend Sogge Heggen Date: Mon, 13 Apr 2015 12:56:12 +0200 Subject: [PATCH 064/225] Update README.md No v3.1 in Q4 2014. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69d2207bd..b3f993569 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ jMonkeyEngine ============= -jMonkeyEngine is a 3D game engine for adventurous Java developers. It’s open source, cross platform and cutting edge. And it is all beautifully documented. The 3.0 branch is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll be frequently submitting stable 3.0.x updates until the major 3.1 version arrives in Q4 2014. +jMonkeyEngine is a 3D game engine for adventurous Java developers. It’s open source, cross platform and cutting edge. And it is all beautifully documented. The 3.0 branch is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll be frequently submitting stable 3.0.x updates until the major 3.1 version arrives. The engine is used by several commercial game studios and computer-science courses. Here's a taste: From b27f86a3aec20e12b40f1336bc5dd09b54a0b270 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Wed, 15 Apr 2015 00:34:59 +0200 Subject: [PATCH 065/225] fix SceneEditTool : - axis pick is now available --- .../src/com/jme3/gde/scenecomposer/SceneEditTool.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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; } From abb1a69d6f050f811c8b78312fd2d735420b617e Mon Sep 17 00:00:00 2001 From: Maselbas Date: Wed, 15 Apr 2015 20:34:57 +0200 Subject: [PATCH 066/225] SDK : SceneComposer - Added support for Axis X,Y,Z movement into the MoveManager --- .../gde/scenecomposer/tools/MoveTool.java | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) 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..c76258313 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; @@ -46,15 +47,7 @@ public class MoveTool extends SceneEditTool { @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - if (!pressed) { - setDefaultAxisMarkerColors(); - pickedPlane = null; // mouse released, reset selection - if (wasDragging) { - actionPerformed(moveManager.makeUndo()); - wasDragging = false; - } - moveManager.reset(); - } + onPrimary(screenCoord, pressed); } @Override @@ -63,20 +56,35 @@ 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) { + onPrimary(screenCoord, pressed); + } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } + + /** + * Called by ActionPrimary and draggedPrimay, improve user feedback + * + * @param screenCoord + * @param pressed + */ + private void onPrimary(Vector2f screenCoord, boolean 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,29 +97,34 @@ 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(); wasDragging = true; } - - @Override - public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - } } From 5ef129248459969de8563731560e32e1a6617d99 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Wed, 15 Apr 2015 23:31:13 +0200 Subject: [PATCH 067/225] SDK : SceneComposer - Added support for Axis X,Y,Z scaling into the ScaleTool, still need some enhancement --- .../gde/scenecomposer/tools/ScaleTool.java | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) 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(); } - + } } From 0d95422d53635fab63c0da93c941bfdc647b9bcc Mon Sep 17 00:00:00 2001 From: Maselbas Date: Thu, 16 Apr 2015 18:22:50 +0200 Subject: [PATCH 068/225] revert some changes in MoveTool --- .../gde/scenecomposer/tools/MoveTool.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) 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 c76258313..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 @@ -47,7 +47,16 @@ public class MoveTool extends SceneEditTool { @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - onPrimary(screenCoord, pressed); + if (!pressed) { + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint + if (wasDragging) { + actionPerformed(moveManager.makeUndo()); + wasDragging = false; + } + moveManager.reset(); + } } @Override @@ -67,21 +76,7 @@ public class MoveTool extends SceneEditTool { @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - onPrimary(screenCoord, pressed); - } - - @Override - public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - } - - /** - * Called by ActionPrimary and draggedPrimay, improve user feedback - * - * @param screenCoord - * @param pressed - */ - private void onPrimary(Vector2f screenCoord, boolean pressed) { - if (!pressed) { + if (!pressed) { setDefaultAxisMarkerColors(); pickedMarker = null; // mouse released, reset selection constraintAxis = Vector3f.UNIT_XYZ; // no constraint @@ -127,4 +122,8 @@ public class MoveTool extends SceneEditTool { wasDragging = true; } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } } From 20ea38e97e64105327708f3a73cc5be17e51a5df Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Thu, 16 Apr 2015 22:18:36 +0200 Subject: [PATCH 069/225] Bugfix: fixed some of the artifacts (unfortunately no all of them) that appeared after IK constraint computation; the algorithm now selects the best found solution among all iterations. --- .../definitions/ConstraintDefinitionIK.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index af1367e4a..35a4e5618 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -110,6 +110,8 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { } } + List bestSolution = new ArrayList(bones.size()); + double bestSolutionDistance = Double.MAX_VALUE; BoneContext topBone = bones.get(0); for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) { for (BoneContext boneContext : bones) { @@ -150,6 +152,20 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD)); Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector distanceFromTarget = e.distance(t); + + if(distanceFromTarget < bestSolutionDistance) { + bestSolutionDistance = distanceFromTarget; + bestSolution.clear(); + for(BoneContext boneContext : bones) { + bestSolution.add(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD)); + } + } + } + + // applying best solution + for(int i=0;i Date: Fri, 17 Apr 2015 16:06:51 +0200 Subject: [PATCH 070/225] Fixed water filter's glsl 100 shader --- .../src/main/resources/Common/MatDefs/Water/Water.frag | 6 +----- .../src/main/resources/Common/MatDefs/Water/Water.j3md | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag index 07a4c6429..cb393dbaa 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag @@ -413,10 +413,6 @@ void main(){ // to calculate the derivatives for all these pixels by using step()! // That way we won't get pixels around the edges of the terrain, // Where the derivatives are undefined - if(position.y > level){ - color = color2; - } - - gl_FragColor = vec4(color,1.0); + gl_FragColor = vec4(mix(color, color2, step(level, position.y)), 1.0); } \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md index 2f4b2b39c..2f188934b 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md @@ -77,6 +77,7 @@ MaterialDef Advanced Water { FragmentShader GLSL120 : Common/MatDefs/Water/Water.frag WorldParameters { + ViewProjectionMatrixInverse } Defines { ENABLE_RIPPLES : UseRipples From 001f3f8f0e788a9df00ebefac9b5eb950e787f20 Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 17 Apr 2015 20:17:04 +0200 Subject: [PATCH 071/225] SDK : Fixed an issue where the shader cide template was not generated properly when creating a shader node definition. --- .../gde/shadernodedefinition/wizard/SNDefWizardIterator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java b/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java index 0fdabe192..6ced1f1f9 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java @@ -102,7 +102,7 @@ public final class SNDefWizardIterator implements WizardDescriptor.Instantiating //Get the template and convert it: FileObject tplSnd = Templates.getTemplate(wizard); - FileObject tplShd = tplSnd.getParent().getChildren()[1]; + FileObject tplShd = tplSnd.getParent().getChildren()[0]; DataObject templateSnd = DataObject.find(tplSnd); DataObject templateShd = DataObject.find(tplShd); From 61ba11d872c81ca57266f9ecdbd3275c5e7fd213 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Fri, 17 Apr 2015 19:00:05 -0400 Subject: [PATCH 072/225] Rework of Android input system to support future expansion and gamepad support. Gamepad support is still a work in progress, but functions. --- ...dler.java => AndroidGestureProcessor.java} | 274 +++---- .../com/jme3/input/android/AndroidInput.java | 686 ------------------ .../input/android/AndroidInputHandler.java | 319 ++++---- .../input/android/AndroidInputHandler14.java | 158 ++++ ...InputHandler.java => AndroidJoyInput.java} | 62 +- .../jme3/input/android/AndroidJoyInput14.java | 108 +++ .../android/AndroidJoystickJoyInput14.java | 403 ++++++++++ .../jme3/input/android/AndroidKeyHandler.java | 140 ---- .../jme3/input/android/AndroidKeyMapping.java | 17 +- .../input/android/AndroidSensorJoyInput.java | 48 +- .../input/android/AndroidTouchHandler.java | 257 ------- .../jme3/input/android/AndroidTouchInput.java | 475 ++++++++++++ ...andler14.java => AndroidTouchInput14.java} | 76 +- .../com/jme3/system/android/OGLESContext.java | 23 +- 14 files changed, 1485 insertions(+), 1561 deletions(-) rename jme3-android/src/main/java/com/jme3/input/android/{AndroidGestureHandler.java => AndroidGestureProcessor.java} (51%) delete mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java create mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java rename jme3-android/src/main/java/com/jme3/input/android/{AndroidJoyInputHandler.java => AndroidJoyInput.java} (84%) create mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java create mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java delete mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java delete mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler.java create mode 100644 jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java rename jme3-android/src/main/java/com/jme3/input/android/{AndroidTouchHandler14.java => AndroidTouchInput14.java} (67%) diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java similarity index 51% rename from jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java rename to jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java index d233836a5..2c0367946 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java @@ -35,314 +35,240 @@ package com.jme3.input.android; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; -import android.view.View; -import com.jme3.input.event.InputEvent; -import com.jme3.input.event.MouseMotionEvent; import com.jme3.input.event.TouchEvent; import java.util.logging.Level; import java.util.logging.Logger; /** * AndroidGestureHandler uses Gesture type listeners to create jME TouchEvents - * for gestures. This class is designed to handle the gestures supported + * for gestures. This class is designed to handle the gestures supported * on Android rev 9 (Android 2.3). Extend this class to add functionality * added by Android after rev 9. - * + * * @author iwgeric */ -public class AndroidGestureHandler implements - GestureDetector.OnGestureListener, +public class AndroidGestureProcessor implements + GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener { - private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName()); - private AndroidInputHandler androidInput; - private GestureDetector gestureDetector; - private ScaleGestureDetector scaleDetector; + private static final Logger logger = Logger.getLogger(AndroidGestureProcessor.class.getName()); + + private AndroidTouchInput touchInput; float gestureDownX = -1f; float gestureDownY = -1f; float scaleStartX = -1f; float scaleStartY = -1f; - public AndroidGestureHandler(AndroidInputHandler androidInput) { - this.androidInput = androidInput; - } - - public void initialize() { - } - - public void destroy() { - setView(null); - } - - public void setView(View view) { - if (view != null) { - gestureDetector = new GestureDetector(view.getContext(), this); - scaleDetector = new ScaleGestureDetector(view.getContext(), this); - } else { - gestureDetector = null; - scaleDetector = null; - } - } - - public void detectGesture(MotionEvent event) { - if (gestureDetector != null && scaleDetector != null) { - gestureDetector.onTouchEvent(event); - scaleDetector.onTouchEvent(event); - } - } - - private int getPointerIndex(MotionEvent event) { - return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - } - - private int getPointerId(MotionEvent event) { - return event.getPointerId(getPointerIndex(event)); + public AndroidGestureProcessor(AndroidTouchInput touchInput) { + this.touchInput = touchInput; } - - private void processEvent(TouchEvent event) { - // Add the touch event - androidInput.addEvent(event); - if (androidInput.isSimulateMouse()) { - InputEvent mouseEvent = generateMouseEvent(event); - if (mouseEvent != null) { - // Add the mouse event - androidInput.addEvent(mouseEvent); - } - } - } - - // TODO: Ring Buffer for mouse events? - private InputEvent generateMouseEvent(TouchEvent event) { - InputEvent inputEvent = null; - int newX; - int newY; - int newDX; - int newDY; - if (androidInput.isMouseEventsInvertX()) { - newX = (int) (androidInput.invertX(event.getX())); - newDX = (int)event.getDeltaX() * -1; - } else { - newX = (int) event.getX(); - newDX = (int)event.getDeltaX(); - } - int wheel = (int) (event.getScaleSpan()); // might need to scale to match mouse wheel - int dWheel = (int) (event.getDeltaScaleSpan()); // might need to scale to match mouse wheel - - if (androidInput.isMouseEventsInvertY()) { - newY = (int) (androidInput.invertY(event.getY())); - newDY = (int)event.getDeltaY() * -1; - } else { - newY = (int) event.getY(); - newDY = (int)event.getDeltaY(); - } - - switch (event.getType()) { - case SCALE_MOVE: - inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, wheel, dWheel); - inputEvent.setTime(event.getTime()); - break; - } - - return inputEvent; - } - /* Events from onGestureListener */ - + + @Override public boolean onDown(MotionEvent event) { // start of all GestureListeners. Not really a gesture by itself // so we don't create an event. // However, reset the scaleInProgress here since this is the beginning // of a series of gesture events. -// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); - gestureDownX = androidInput.getJmeX(event.getX()); - gestureDownY = androidInput.invertY(androidInput.getJmeY(event.getY())); +// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + gestureDownX = touchInput.getJmeX(event.getX()); + gestureDownY = touchInput.invertY(touchInput.getJmeY(event.getY())); return true; } + @Override public boolean onSingleTapUp(MotionEvent event) { // Up of single tap. May be followed by a double tap later. // use onSingleTapConfirmed instead. -// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); +// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); return true; } + @Override public void onShowPress(MotionEvent event) { -// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); - float jmeX = androidInput.getJmeX(event.getX()); - float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); +// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); } + @Override public void onLongPress(MotionEvent event) { -// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); - float jmeX = androidInput.getJmeX(event.getX()); - float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); +// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); } + @Override public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) { // if not scaleInProgess, send scroll events. This is to avoid sending // scroll events when one of the fingers is lifted just before the other one. // Avoids sending the scroll for that brief period of time. // Return true so that the next event doesn't accumulate the distX and distY values. - // Apparantly, both distX and distY are negative. + // Apparantly, both distX and distY are negative. // Negate distX to get the real value, but leave distY negative to compensate // for the fact that jME has y=0 at bottom where Android has y=0 at top. -// if (!scaleInProgress) { - if (!scaleDetector.isInProgress()) { -// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}", -// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY}); + if (!touchInput.getScaleDetector().isInProgress()) { +// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}", +// new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY}); - float jmeX = androidInput.getJmeX(endEvent.getX()); - float jmeY = androidInput.invertY(androidInput.getJmeY(endEvent.getY())); - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); - touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, androidInput.getJmeX(-distX), androidInput.getJmeY(distY)); - touchEvent.setPointerId(getPointerId(endEvent)); + float jmeX = touchInput.getJmeX(endEvent.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(endEvent.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, touchInput.getJmeX(-distX), touchInput.getJmeY(distY)); + touchEvent.setPointerId(touchInput.getPointerId(endEvent)); touchEvent.setTime(endEvent.getEventTime()); touchEvent.setPressure(endEvent.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); } return true; } + @Override public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) { // Fling happens only once at the end of the gesture (all fingers up). // Fling returns the velocity of the finger movement in pixels/sec. // Therefore, the dX and dY values are actually velocity instead of distance values // Since this does not track the movement, use the start position and velocity values. - -// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}", -// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY}); - float jmeX = androidInput.getJmeX(startEvent.getX()); - float jmeY = androidInput.invertY(androidInput.getJmeY(startEvent.getY())); - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); +// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}", +// new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY}); + + float jmeX = touchInput.getJmeX(startEvent.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(startEvent.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY); - touchEvent.setPointerId(getPointerId(endEvent)); + touchEvent.setPointerId(touchInput.getPointerId(endEvent)); touchEvent.setTime(endEvent.getEventTime()); touchEvent.setPressure(endEvent.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); return true; } /* Events from onDoubleTapListener */ - + + @Override public boolean onSingleTapConfirmed(MotionEvent event) { // Up of single tap when no double tap followed. -// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); - float jmeX = androidInput.getJmeX(event.getX()); - float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); +// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); return true; } + @Override public boolean onDoubleTap(MotionEvent event) { //The down motion event of the first tap of the double-tap - // We could use this event to fire off a double tap event, or use + // We could use this event to fire off a double tap event, or use // DoubleTapEvent with a check for the UP action -// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); - float jmeX = androidInput.getJmeX(event.getX()); - float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); +// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + float jmeX = touchInput.getJmeX(event.getX()); + float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY())); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0); - touchEvent.setPointerId(getPointerId(event)); + touchEvent.setPointerId(touchInput.getPointerId(event)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure()); - processEvent(touchEvent); + touchInput.addEvent(touchEvent); return true; } + @Override public boolean onDoubleTapEvent(MotionEvent event) { //Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events. // this means it will get called multiple times for a single double tap -// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}", -// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); -// if (getAction(event) == MotionEvent.ACTION_UP) { -// TouchEvent touchEvent = touchEventPool.getNextFreeEvent(); -// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0); -// touchEvent.setPointerId(getPointerId(event)); -// touchEvent.setTime(event.getEventTime()); -// touchEvent.setPressure(event.getPressure()); -// processEvent(touchEvent); -// } +// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()}); + if (touchInput.getAction(event) == MotionEvent.ACTION_UP) { + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), touchInput.invertY(event.getY()), 0, 0); + touchEvent.setPointerId(touchInput.getPointerId(event)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure()); + touchInput.addEvent(touchEvent); + } return true; } /* Events from ScaleGestureDetector */ - + + @Override public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { // Scale uses a focusX and focusY instead of x and y. Focus is the middle // of the fingers. Therefore, use the x and y values from the Down event // so that the x and y values don't jump to the middle position. // return true or all gestures for this beginning event will be discarded - logger.log(Level.INFO, "onScaleBegin"); +// logger.log(Level.INFO, "onScaleBegin"); scaleStartX = gestureDownX; scaleStartY = gestureDownY; - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f); touchEvent.setPointerId(0); touchEvent.setTime(scaleGestureDetector.getEventTime()); touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); touchEvent.setDeltaScaleSpan(0f); touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); - touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); - processEvent(touchEvent); - + touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress()); + touchInput.addEvent(touchEvent); + return true; } + @Override public boolean onScale(ScaleGestureDetector scaleGestureDetector) { // return true or all gestures for this event will be accumulated - logger.log(Level.INFO, "onScale"); +// logger.log(Level.INFO, "onScale"); scaleStartX = gestureDownX; scaleStartY = gestureDownY; - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f); touchEvent.setPointerId(0); touchEvent.setTime(scaleGestureDetector.getEventTime()); touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); - touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); - processEvent(touchEvent); + touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress()); + touchInput.addEvent(touchEvent); return true; } + @Override public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { - logger.log(Level.INFO, "onScaleEnd"); +// logger.log(Level.INFO, "onScaleEnd"); scaleStartX = gestureDownX; scaleStartY = gestureDownY; - TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + TouchEvent touchEvent = touchInput.getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f); touchEvent.setPointerId(0); touchEvent.setTime(scaleGestureDetector.getEventTime()); touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); - touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); - processEvent(touchEvent); + touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress()); + touchInput.addEvent(touchEvent); } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java deleted file mode 100644 index 02fef6b0f..000000000 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java +++ /dev/null @@ -1,686 +0,0 @@ -package com.jme3.input.android; - -import android.view.*; -import com.jme3.input.KeyInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.TouchInput; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; -import com.jme3.input.event.TouchEvent; -import com.jme3.input.event.TouchEvent.Type; -import com.jme3.math.Vector2f; -import com.jme3.system.AppSettings; -import com.jme3.util.RingBuffer; -import java.util.HashMap; -import java.util.logging.Logger; - -/** - * AndroidInput is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs - * @author larynx - * - */ -public class AndroidInput implements - TouchInput, - View.OnTouchListener, - View.OnKeyListener, - GestureDetector.OnGestureListener, - GestureDetector.OnDoubleTapListener, - ScaleGestureDetector.OnScaleGestureListener { - - final private static int MAX_EVENTS = 1024; - // Custom settings - public boolean mouseEventsEnabled = true; - public boolean mouseEventsInvertX = false; - public boolean mouseEventsInvertY = false; - public boolean keyboardEventsEnabled = false; - public boolean dontSendHistory = false; - // Used to transfer events from android thread to GLThread - final private RingBuffer eventQueue = new RingBuffer(MAX_EVENTS); - final private RingBuffer eventPoolUnConsumed = new RingBuffer(MAX_EVENTS); - final private RingBuffer eventPool = new RingBuffer(MAX_EVENTS); - final private HashMap lastPositions = new HashMap(); - // Internal - private View view; - private ScaleGestureDetector scaledetector; - private boolean scaleInProgress = false; - private GestureDetector detector; - private int lastX; - private int lastY; - private final static Logger logger = Logger.getLogger(AndroidInput.class.getName()); - private boolean isInitialized = false; - private RawInputListener listener = null; - private static final int[] ANDROID_TO_JME = { - 0x0, // unknown - 0x0, // key code soft left - 0x0, // key code soft right - KeyInput.KEY_HOME, - KeyInput.KEY_ESCAPE, // key back - 0x0, // key call - 0x0, // key endcall - KeyInput.KEY_0, - KeyInput.KEY_1, - KeyInput.KEY_2, - KeyInput.KEY_3, - KeyInput.KEY_4, - KeyInput.KEY_5, - KeyInput.KEY_6, - KeyInput.KEY_7, - KeyInput.KEY_8, - KeyInput.KEY_9, - KeyInput.KEY_MULTIPLY, - 0x0, // key pound - KeyInput.KEY_UP, - KeyInput.KEY_DOWN, - KeyInput.KEY_LEFT, - KeyInput.KEY_RIGHT, - KeyInput.KEY_RETURN, // dpad center - 0x0, // volume up - 0x0, // volume down - KeyInput.KEY_POWER, // power (?) - 0x0, // camera - 0x0, // clear - KeyInput.KEY_A, - KeyInput.KEY_B, - KeyInput.KEY_C, - KeyInput.KEY_D, - KeyInput.KEY_E, - KeyInput.KEY_F, - KeyInput.KEY_G, - KeyInput.KEY_H, - KeyInput.KEY_I, - KeyInput.KEY_J, - KeyInput.KEY_K, - KeyInput.KEY_L, - KeyInput.KEY_M, - KeyInput.KEY_N, - KeyInput.KEY_O, - KeyInput.KEY_P, - KeyInput.KEY_Q, - KeyInput.KEY_R, - KeyInput.KEY_S, - KeyInput.KEY_T, - KeyInput.KEY_U, - KeyInput.KEY_V, - KeyInput.KEY_W, - KeyInput.KEY_X, - KeyInput.KEY_Y, - KeyInput.KEY_Z, - KeyInput.KEY_COMMA, - KeyInput.KEY_PERIOD, - KeyInput.KEY_LMENU, - KeyInput.KEY_RMENU, - KeyInput.KEY_LSHIFT, - KeyInput.KEY_RSHIFT, - // 0x0, // fn - // 0x0, // cap (?) - - KeyInput.KEY_TAB, - KeyInput.KEY_SPACE, - 0x0, // sym (?) symbol - 0x0, // explorer - 0x0, // envelope - KeyInput.KEY_RETURN, // newline/enter - KeyInput.KEY_DELETE, - KeyInput.KEY_GRAVE, - KeyInput.KEY_MINUS, - KeyInput.KEY_EQUALS, - KeyInput.KEY_LBRACKET, - KeyInput.KEY_RBRACKET, - KeyInput.KEY_BACKSLASH, - KeyInput.KEY_SEMICOLON, - KeyInput.KEY_APOSTROPHE, - KeyInput.KEY_SLASH, - KeyInput.KEY_AT, // at (@) - KeyInput.KEY_NUMLOCK, //0x0, // num - 0x0, //headset hook - 0x0, //focus - KeyInput.KEY_ADD, - KeyInput.KEY_LMETA, //menu - 0x0,//notification - 0x0,//search - 0x0,//media play/pause - 0x0,//media stop - 0x0,//media next - 0x0,//media previous - 0x0,//media rewind - 0x0,//media fastforward - 0x0,//mute - }; - - public AndroidInput() { - } - - public void setView(View view) { - this.view = view; - if (view != null) { - detector = new GestureDetector(null, this, null, false); - scaledetector = new ScaleGestureDetector(view.getContext(), this); - view.setOnTouchListener(this); - view.setOnKeyListener(this); - } - } - - private TouchEvent getNextFreeTouchEvent() { - return getNextFreeTouchEvent(false); - } - - /** - * Fetches a touch event from the reuse pool - * @param wait if true waits for a reusable event to get available/released - * by an other thread, if false returns a new one if needed. - * - * @return a usable TouchEvent - */ - private TouchEvent getNextFreeTouchEvent(boolean wait) { - TouchEvent evt = null; - synchronized (eventPoolUnConsumed) { - int size = eventPoolUnConsumed.size(); - while (size > 0) { - evt = eventPoolUnConsumed.pop(); - if (!evt.isConsumed()) { - eventPoolUnConsumed.push(evt); - evt = null; - } else { - break; - } - size--; - } - } - - if (evt == null) { - if (eventPool.isEmpty() && wait) { - logger.warning("eventPool buffer underrun"); - boolean isEmpty; - do { - synchronized (eventPool) { - isEmpty = eventPool.isEmpty(); - } - try { - Thread.sleep(50); - } catch (InterruptedException e) { - } - } while (isEmpty); - synchronized (eventPool) { - evt = eventPool.pop(); - } - } else if (eventPool.isEmpty()) { - evt = new TouchEvent(); - logger.warning("eventPool buffer underrun"); - } else { - synchronized (eventPool) { - evt = eventPool.pop(); - } - } - } - return evt; - } - - /** - * onTouch gets called from android thread on touchpad events - */ - public boolean onTouch(View view, MotionEvent event) { - if (view != this.view) { - return false; - } - boolean bWasHandled = false; - TouchEvent touch; - // System.out.println("native : " + event.getAction()); - int action = event.getAction() & MotionEvent.ACTION_MASK; - int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - int pointerId = event.getPointerId(pointerIndex); - Vector2f lastPos = lastPositions.get(pointerId); - - // final int historySize = event.getHistorySize(); - //final int pointerCount = event.getPointerCount(); - switch (action) { - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_DOWN: - touch = getNextFreeTouchEvent(); - touch.set(Type.DOWN, event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex), 0, 0); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - processEvent(touch); - - lastPos = new Vector2f(event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex)); - lastPositions.put(pointerId, lastPos); - - bWasHandled = true; - break; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - touch = getNextFreeTouchEvent(); - touch.set(Type.UP, event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex), 0, 0); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - processEvent(touch); - lastPositions.remove(pointerId); - - bWasHandled = true; - break; - case MotionEvent.ACTION_MOVE: - // Convert all pointers into events - for (int p = 0; p < event.getPointerCount(); p++) { - lastPos = lastPositions.get(event.getPointerId(p)); - if (lastPos == null) { - lastPos = new Vector2f(event.getX(p), view.getHeight() - event.getY(p)); - lastPositions.put(event.getPointerId(p), lastPos); - } - - float dX = event.getX(p) - lastPos.x; - float dY = view.getHeight() - event.getY(p) - lastPos.y; - if (dX != 0 || dY != 0) { - touch = getNextFreeTouchEvent(); - touch.set(Type.MOVE, event.getX(p), view.getHeight() - event.getY(p), dX, dY); - touch.setPointerId(event.getPointerId(p)); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(p)); - touch.setScaleSpanInProgress(scaleInProgress); - processEvent(touch); - lastPos.set(event.getX(p), view.getHeight() - event.getY(p)); - } - } - bWasHandled = true; - break; - case MotionEvent.ACTION_OUTSIDE: - break; - - } - - // Try to detect gestures - this.detector.onTouchEvent(event); - this.scaledetector.onTouchEvent(event); - - return bWasHandled; - } - - /** - * onKey gets called from android thread on key events - */ - public boolean onKey(View view, int keyCode, KeyEvent event) { - if (view != this.view) { - return false; - } - - if (event.getAction() == KeyEvent.ACTION_DOWN) { - TouchEvent evt; - evt = getNextFreeTouchEvent(); - evt.set(TouchEvent.Type.KEY_DOWN); - evt.setKeyCode(keyCode); - evt.setCharacters(event.getCharacters()); - evt.setTime(event.getEventTime()); - - // Send the event - processEvent(evt); - - // Handle all keys ourself except Volume Up/Down - if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { - return false; - } else { - return true; - } - } else if (event.getAction() == KeyEvent.ACTION_UP) { - TouchEvent evt; - evt = getNextFreeTouchEvent(); - evt.set(TouchEvent.Type.KEY_UP); - evt.setKeyCode(keyCode); - evt.setCharacters(event.getCharacters()); - evt.setTime(event.getEventTime()); - - // Send the event - processEvent(evt); - - // Handle all keys ourself except Volume Up/Down - if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { - return false; - } else { - return true; - } - } else { - return false; - } - } - - public void loadSettings(AppSettings settings) { - mouseEventsEnabled = settings.isEmulateMouse(); - mouseEventsInvertX = settings.isEmulateMouseFlipX(); - mouseEventsInvertY = settings.isEmulateMouseFlipY(); - } - - // ----------------------------------------- - // JME3 Input interface - @Override - public void initialize() { - TouchEvent item; - for (int i = 0; i < MAX_EVENTS; i++) { - item = new TouchEvent(); - eventPool.push(item); - } - isInitialized = true; - } - - @Override - public void destroy() { - isInitialized = false; - - // Clean up queues - while (!eventPool.isEmpty()) { - eventPool.pop(); - } - while (!eventQueue.isEmpty()) { - eventQueue.pop(); - } - - - this.view = null; - } - - @Override - public boolean isInitialized() { - return isInitialized; - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return System.nanoTime(); - } - // ----------------------------------------- - - private void processEvent(TouchEvent event) { - synchronized (eventQueue) { - //Discarding events when the ring buffer is full to avoid buffer overflow. - if(eventQueue.size()< MAX_EVENTS){ - eventQueue.push(event); - } - - } - } - - // --------------- INSIDE GLThread --------------- - @Override - public void update() { - generateEvents(); - } - - private void generateEvents() { - if (listener != null) { - TouchEvent event; - MouseButtonEvent btn; - MouseMotionEvent mot; - int newX; - int newY; - - while (!eventQueue.isEmpty()) { - synchronized (eventQueue) { - event = eventQueue.pop(); - } - if (event != null) { - listener.onTouchEvent(event); - - if (mouseEventsEnabled) { - if (mouseEventsInvertX) { - newX = view.getWidth() - (int) event.getX(); - } else { - newX = (int) event.getX(); - } - - if (mouseEventsInvertY) { - newY = view.getHeight() - (int) event.getY(); - } else { - newY = (int) event.getY(); - } - - switch (event.getType()) { - case DOWN: - // Handle mouse down event - btn = new MouseButtonEvent(0, true, newX, newY); - btn.setTime(event.getTime()); - listener.onMouseButtonEvent(btn); - // Store current pos - lastX = -1; - lastY = -1; - break; - - case UP: - // Handle mouse up event - btn = new MouseButtonEvent(0, false, newX, newY); - btn.setTime(event.getTime()); - listener.onMouseButtonEvent(btn); - // Store current pos - lastX = -1; - lastY = -1; - break; - - case SCALE_MOVE: - if (lastX != -1 && lastY != -1) { - newX = lastX; - newY = lastY; - } - int wheel = (int) (event.getScaleSpan() / 4f); // scale to match mouse wheel - int dwheel = (int) (event.getDeltaScaleSpan() / 4f); // scale to match mouse wheel - mot = new MouseMotionEvent(newX, newX, 0, 0, wheel, dwheel); - mot.setTime(event.getTime()); - listener.onMouseMotionEvent(mot); - lastX = newX; - lastY = newY; - - break; - - case MOVE: - if (event.isScaleSpanInProgress()) { - break; - } - - int dx; - int dy; - if (lastX != -1) { - dx = newX - lastX; - dy = newY - lastY; - } else { - dx = 0; - dy = 0; - } - - mot = new MouseMotionEvent(newX, newY, dx, dy, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); - mot.setTime(event.getTime()); - listener.onMouseMotionEvent(mot); - lastX = newX; - lastY = newY; - - break; - } - } - } - - if (event.isConsumed() == false) { - synchronized (eventPoolUnConsumed) { - eventPoolUnConsumed.push(event); - } - - } else { - synchronized (eventPool) { - eventPool.push(event); - } - } - } - - } - } - // --------------- ENDOF INSIDE GLThread --------------- - - // --------------- Gesture detected callback events --------------- - public boolean onDown(MotionEvent event) { - return false; - } - - public void onLongPress(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.LONGPRESSED, event.getX(), view.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - } - - public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.FLING, event.getX(), view.getHeight() - event.getY(), vx, vy); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - - return true; - } - - public boolean onSingleTapConfirmed(MotionEvent event) { - //Nothing to do here the tap has already been detected. - return false; - } - - public boolean onDoubleTap(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.DOUBLETAP, event.getX(), view.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - return true; - } - - public boolean onDoubleTapEvent(MotionEvent event) { - return false; - } - - public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { - scaleInProgress = true; - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(scaleGestureDetector.getEventTime()); - touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); - touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); - touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); - touch.setScaleSpanInProgress(scaleInProgress); - processEvent(touch); - // System.out.println("scaleBegin"); - - return true; - } - - public boolean onScale(ScaleGestureDetector scaleGestureDetector) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), view.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(scaleGestureDetector.getEventTime()); - touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); - touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); - touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); - touch.setScaleSpanInProgress(scaleInProgress); - processEvent(touch); - // System.out.println("scale"); - - return false; - } - - public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { - scaleInProgress = false; - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), view.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(scaleGestureDetector.getEventTime()); - touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); - touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); - touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); - touch.setScaleSpanInProgress(scaleInProgress); - processEvent(touch); - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCROLL, e1.getX(), view.getHeight() - e1.getY(), distanceX, distanceY * (-1)); - touch.setPointerId(0); - touch.setTime(e1.getEventTime()); - processEvent(touch); - //System.out.println("scroll " + e1.getPointerCount()); - return false; - } - - public void onShowPress(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SHOWPRESS, event.getX(), view.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - } - - public boolean onSingleTapUp(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.TAP, event.getX(), view.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - return true; - } - - @Override - public void setSimulateKeyboard(boolean simulate) { - keyboardEventsEnabled = simulate; - } - - @Override - public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; - } - - /** - * @deprecated Use {@link #getSimulateMouse()}; - */ - @Deprecated - public boolean isMouseEventsEnabled() { - return mouseEventsEnabled; - } - - @Deprecated - public void setMouseEventsEnabled(boolean mouseEventsEnabled) { - this.mouseEventsEnabled = mouseEventsEnabled; - } - - public boolean isMouseEventsInvertY() { - return mouseEventsInvertY; - } - - public void setMouseEventsInvertY(boolean mouseEventsInvertY) { - this.mouseEventsInvertY = mouseEventsInvertY; - } - - public boolean isMouseEventsInvertX() { - return mouseEventsInvertX; - } - - public void setMouseEventsInvertX(boolean mouseEventsInvertX) { - this.mouseEventsInvertX = mouseEventsInvertX; - } - - public void setSimulateMouse(boolean simulate) { - mouseEventsEnabled = simulate; - } - - public boolean getSimulateMouse() { - return isSimulateMouse(); - } - - public boolean isSimulateMouse() { - return mouseEventsEnabled; - } - - public boolean isSimulateKeyboard() { - return keyboardEventsEnabled; - } - -} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java index 202907316..9f4729c66 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -33,231 +33,206 @@ package com.jme3.input.android; import android.opengl.GLSurfaceView; -import android.os.Build; +import android.view.GestureDetector; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; import android.view.View; -import com.jme3.input.RawInputListener; +import com.jme3.input.JoyInput; import com.jme3.input.TouchInput; -import com.jme3.input.event.InputEvent; -import com.jme3.input.event.KeyInputEvent; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; -import com.jme3.input.event.TouchEvent; import com.jme3.system.AppSettings; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import java.util.logging.Logger; /** * AndroidInput is the main class that connects the Android system - * inputs to jME. It serves as the manager that gathers inputs from the various - * Android input methods and provides them to jME's InputManager. + * inputs to jME. It receives the inputs from the Android View and passes them + * to the appropriate classes based on the source of the input.
+ * This class is to be extended when new functionality is released in Android. * * @author iwgeric */ -public class AndroidInputHandler implements TouchInput { - private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); - - // Custom settings - private boolean mouseEventsEnabled = true; - private boolean mouseEventsInvertX = false; - private boolean mouseEventsInvertY = false; - private boolean keyboardEventsEnabled = false; - private boolean dontSendHistory = false; +public class AndroidInputHandler implements View.OnTouchListener, + View.OnKeyListener { + private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); - // Internal - private GLSurfaceView view; - private AndroidTouchHandler touchHandler; - private AndroidKeyHandler keyHandler; - private AndroidGestureHandler gestureHandler; - private boolean initialized = false; - private RawInputListener listener = null; - private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); - private final static int MAX_TOUCH_EVENTS = 1024; - private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); - private float scaleX = 1f; - private float scaleY = 1f; + protected GLSurfaceView view; + protected AndroidTouchInput touchInput; + protected AndroidJoyInput joyInput; public AndroidInputHandler() { - int buildVersion = Build.VERSION.SDK_INT; - logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); - if (buildVersion >= 14) { - // add support for onHover and GenericMotionEvent (ie. gamepads) - gestureHandler = new AndroidGestureHandler(this); - touchHandler = new AndroidTouchHandler14(this, gestureHandler); - keyHandler = new AndroidKeyHandler(this); - } else if (buildVersion >= 8){ - gestureHandler = new AndroidGestureHandler(this); - touchHandler = new AndroidTouchHandler(this, gestureHandler); - keyHandler = new AndroidKeyHandler(this); - } - } - - public AndroidInputHandler(AndroidTouchHandler touchInput, - AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) { - this.touchHandler = touchInput; - this.keyHandler = keyInput; - this.gestureHandler = gestureHandler; + touchInput = new AndroidTouchInput(this); + joyInput = new AndroidJoyInput(this); } public void setView(View view) { - if (touchHandler != null) { - touchHandler.setView(view); + if (this.view != null && view != null && this.view.equals(view)) { + return; } - if (keyHandler != null) { - keyHandler.setView(view); - } - if (gestureHandler != null) { - gestureHandler.setView(view); + + if (this.view != null) { + removeListeners(this.view); } + this.view = (GLSurfaceView)view; - } - public View getView() { - return view; - } + if (this.view != null) { + addListeners(this.view); + } - public float invertX(float origX) { - return getJmeX(view.getWidth()) - origX; + joyInput.setView((GLSurfaceView)view); } - public float invertY(float origY) { - return getJmeY(view.getHeight()) - origY; + public View getView() { + return view; } - public float getJmeX(float origX) { - return origX * scaleX; + protected void removeListeners(GLSurfaceView view) { + view.setOnTouchListener(null); + view.setOnKeyListener(null); + touchInput.setGestureDetector(null); + touchInput.setScaleDetector(null); } - public float getJmeY(float origY) { - return origY * scaleY; + protected void addListeners(GLSurfaceView view) { + view.setOnTouchListener(this); + view.setOnKeyListener(this); + AndroidGestureProcessor gestureHandler = new AndroidGestureProcessor(touchInput); + touchInput.setGestureDetector(new GestureDetector( + view.getContext(), gestureHandler)); + touchInput.setScaleDetector(new ScaleGestureDetector( + view.getContext(), gestureHandler)); } public void loadSettings(AppSettings settings) { - keyboardEventsEnabled = settings.isEmulateKeyboard(); - mouseEventsEnabled = settings.isEmulateMouse(); - mouseEventsInvertX = settings.isEmulateMouseFlipX(); - mouseEventsInvertY = settings.isEmulateMouseFlipY(); - - // view width and height are 0 until the view is displayed on the screen - if (view.getWidth() != 0 && view.getHeight() != 0) { - scaleX = (float)settings.getWidth() / (float)view.getWidth(); - scaleY = (float)settings.getHeight() / (float)view.getHeight(); - } - logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", - new Object[]{scaleX, scaleY}); + touchInput.loadSettings(settings); + } + + public TouchInput getTouchInput() { + return touchInput; + } + + public JoyInput getJoyInput() { + return joyInput; + } + + /* + * Android input events include the source from which the input came from. + * We must look at the source of the input event to determine which type + * of jME input it belongs to. + * If the input is from a gamepad or joystick source, the event is sent + * to the JoyInput class to convert the event into jME joystick events. + *
+ * If the input is from a touchscreen source, the event is sent to the + * TouchProcessor to convert the event into touch events. + * The TouchProcessor also converts the events into Mouse and Key events + * if AppSettings is set to simulate Mouse or Keyboard events. + * + * Android reports the source as a bitmask as shown below.
+ * + * InputDevice Sources + * 0000 0000 0000 0000 0000 0000 0000 0000 - 32 bit bitmask + * + * 0000 0000 0000 0000 0000 0000 1111 1111 - SOURCE_CLASS_MASK (0x000000ff) + * 0000 0000 0000 0000 0000 0000 0000 0000 - SOURCE_CLASS_NONE (0x00000000) + * 0000 0000 0000 0000 0000 0000 0000 0001 - SOURCE_CLASS_BUTTON (0x00000001) + * 0000 0000 0000 0000 0000 0000 0000 0010 - SOURCE_CLASS_POINTER (0x00000002) + * 0000 0000 0000 0000 0000 0000 0000 0100 - SOURCE_CLASS_TRACKBALL (0x00000004) + * 0000 0000 0000 0000 0000 0000 0000 1000 - SOURCE_CLASS_POSITION (0x00000008) + * 0000 0000 0000 0000 0000 0000 0001 0000 - SOURCE_CLASS_JOYSTICK (0x00000010) + * + * 1111 1111 1111 1111 1111 1111 0000 0000 - Source_Any (0xffffff00) + * 0000 0000 0000 0000 0000 0000 0000 0000 - SOURCE_UNKNOWN (0x00000000) + * 0000 0000 0000 0000 0000 0001 0000 0001 - SOURCE_KEYBOARD (0x00000101) + * 0000 0000 0000 0000 0000 0010 0000 0001 - SOURCE_DPAD (0x00000201) + * 0000 0000 0000 0000 0000 0100 0000 0001 - SOURCE_GAMEPAD (0x00000401) + * 0000 0000 0000 0000 0001 0000 0000 0010 - SOURCE_TOUCHSCREEN (0x00001002) + * 0000 0000 0000 0000 0010 0000 0000 0010 - SOURCE_MOUSE (0x00002002) + * 0000 0000 0000 0000 0100 0000 0000 0010 - SOURCE_STYLUS (0x00004002) + * 0000 0000 0000 0001 0000 0000 0000 0100 - SOURCE_TRACKBALL (0x00010004) + * 0000 0000 0001 0000 0000 0000 0000 1000 - SOURCE_TOUCHPAD (0x00100008) + * 0000 0000 0010 0000 0000 0000 0000 0000 - SOURCE_TOUCH_NAVIGATION (0x00200000) + * 0000 0001 0000 0000 0000 0000 0001 0000 - SOURCE_JOYSTICK (0x01000010) + * 0000 0010 0000 0000 0000 0000 0000 0001 - SOURCE_HDMI (0x02000001) + * + * Example values reported by Android for Source + * 4,098 = 0x00001002 = + * 0000 0000 0000 0000 0001 0000 0000 0010 - SOURCE_CLASS_POINTER + * SOURCE_TOUCHSCREEN + * 1,281 = 0x00000501 = + * 0000 0000 0000 0000 0000 0101 0000 0001 - SOURCE_CLASS_BUTTON + * SOURCE_KEYBOARD + * SOURCE_GAMEPAD + * 16,777,232 = 0x01000010 = + * 0000 0001 0000 0000 0000 0000 0001 0000 - SOURCE_CLASS_JOYSTICK + * SOURCE_JOYSTICK + * + * 16,778,513 = 0x01000511 = + * 0000 0001 0000 0000 0000 0101 0001 0001 - SOURCE_CLASS_BUTTON + * SOURCE_CLASS_JOYSTICK + * SOURCE_GAMEPAD + * SOURCE_KEYBOARD + * SOURCE_JOYSTICK + * + * 257 = 0x00000101 = + * 0000 0000 0000 0000 0000 0001 0000 0001 - SOURCE_CLASS_BUTTON + * SOURCE_KEYBOARD + * + * + * + */ - } - // ----------------------------------------- - // JME3 Input interface @Override - public void initialize() { - touchEventPool.initialize(); - if (touchHandler != null) { - touchHandler.initialize(); - } - if (keyHandler != null) { - keyHandler.initialize(); - } - if (gestureHandler != null) { - gestureHandler.initialize(); + public boolean onTouch(View view, MotionEvent event) { + if (view != getView()) { + return false; } - initialized = true; - } + boolean consumed = false; - @Override - public void destroy() { - initialized = false; + int source = event.getSource(); +// logger.log(Level.INFO, "onTouch source: {0}", source); - touchEventPool.destroy(); - if (touchHandler != null) { - touchHandler.destroy(); - } - if (keyHandler != null) { - keyHandler.destroy(); - } - if (gestureHandler != null) { - gestureHandler.destroy(); - } - - setView(null); - } - - @Override - public boolean isInitialized() { - return initialized; - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return System.nanoTime(); - } + boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN); +// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); - public void update() { - if (listener != null) { - InputEvent inputEvent; - - while ((inputEvent = inputEventQueue.poll()) != null) { - if (inputEvent instanceof TouchEvent) { - listener.onTouchEvent((TouchEvent)inputEvent); - } else if (inputEvent instanceof MouseButtonEvent) { - listener.onMouseButtonEvent((MouseButtonEvent)inputEvent); - } else if (inputEvent instanceof MouseMotionEvent) { - listener.onMouseMotionEvent((MouseMotionEvent)inputEvent); - } else if (inputEvent instanceof KeyInputEvent) { - listener.onKeyEvent((KeyInputEvent)inputEvent); - } - } + if (isTouch && touchInput != null) { + // send the event to the touch processor + consumed = touchInput.onTouch(event); } - } - // ----------------------------------------- + return consumed; - public TouchEvent getFreeTouchEvent() { - return touchEventPool.getNextFreeEvent(); } - public void addEvent(InputEvent event) { - inputEventQueue.add(event); - if (event instanceof TouchEvent) { - touchEventPool.storeEvent((TouchEvent)event); + @Override + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (view != getView()) { + return false; } - } - public void setSimulateMouse(boolean simulate) { - this.mouseEventsEnabled = simulate; - } + boolean consumed = false; - public boolean isSimulateMouse() { - return mouseEventsEnabled; - } + int source = event.getSource(); +// logger.log(Level.INFO, "onKey source: {0}", source); - public boolean isMouseEventsInvertX() { - return mouseEventsInvertX; - } + boolean isTouch = + ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) || + ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD); +// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); - public boolean isMouseEventsInvertY() { - return mouseEventsInvertY; - } - - public void setSimulateKeyboard(boolean simulate) { - this.keyboardEventsEnabled = simulate; - } + if (touchInput != null) { + consumed = touchInput.onKey(event); + } - public boolean isSimulateKeyboard() { - return keyboardEventsEnabled; - } + return consumed; - public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java new file mode 100644 index 000000000..114bb98fd --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.opengl.GLSurfaceView; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidInputHandler14 extends AndroidInputHandler to + * add the onHover and onGenericMotion events that where added in Android rev 14 (Android 4.0).
+ * The onGenericMotion events are the main interface to Joystick axes. They + * were actually released in Android rev 12. + * + * @author iwgeric + */ +public class AndroidInputHandler14 extends AndroidInputHandler implements View.OnHoverListener, + View.OnGenericMotionListener { + + private static final Logger logger = Logger.getLogger(AndroidInputHandler14.class.getName()); + + public AndroidInputHandler14() { + touchInput = new AndroidTouchInput14(this); + joyInput = new AndroidJoyInput14(this); + } + + @Override + protected void removeListeners(GLSurfaceView view) { + super.removeListeners(view); + view.setOnHoverListener(null); + view.setOnGenericMotionListener(null); + } + + @Override + protected void addListeners(GLSurfaceView view) { + super.addListeners(view); + view.setOnHoverListener(this); + view.setOnGenericMotionListener(this); + } + + @Override + public boolean onHover(View view, MotionEvent event) { + if (view != getView()) { + return false; + } + + boolean consumed = false; + + int source = event.getSource(); +// logger.log(Level.INFO, "onTouch source: {0}", source); + + boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN); +// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); + + if (isTouch && touchInput != null) { + // send the event to the touch processor + consumed = ((AndroidTouchInput14)touchInput).onHover(event); + } + + return consumed; + } + + @Override + public boolean onGenericMotion(View view, MotionEvent event) { + if (view != getView()) { + return false; + } + + boolean consumed = false; + + int source = event.getSource(); +// logger.log(Level.INFO, "onGenericMotion source: {0}", source); + + boolean isJoystick = + ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); + + if (isJoystick && joyInput != null) { +// logger.log(Level.INFO, "onGenericMotion source: {0}, isJoystick: {1}", +// new Object[]{source, isJoystick}); + // send the event to the touch processor + consumed = consumed || ((AndroidJoyInput14)joyInput).onGenericMotion(event); + } + + return consumed; + } + + @Override + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (view != getView()) { + return false; + } + + boolean consumed = false; + +// logger.log(Level.INFO, "onKey keyCode: {0}, action: {1}, event: {2}", +// new Object[]{KeyEvent.keyCodeToString(keyCode), event.getAction(), event}); + int source = event.getSource(); +// logger.log(Level.INFO, "onKey source: {0}", source); + + boolean isTouch = + ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) || + ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD); + boolean isJoystick = + ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); + + if (isTouch && touchInput != null) { +// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", +// new Object[]{source, isTouch}); + consumed = touchInput.onKey(event); + } + if (isJoystick && joyInput != null) { +// logger.log(Level.INFO, "onKey source: {0}, isJoystick: {1}", +// new Object[]{source, isJoystick}); + consumed = consumed || ((AndroidJoyInput14)joyInput).onKey(event); + } + + return consumed; + + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java similarity index 84% rename from jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java rename to jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java index 2c3a1d74e..e31504520 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java @@ -33,9 +33,7 @@ package com.jme3.input.android; import android.content.Context; import android.opengl.GLSurfaceView; -import android.os.Build; import android.os.Vibrator; -import android.view.View; import com.jme3.input.InputManager; import com.jme3.input.JoyInput; import com.jme3.input.Joystick; @@ -79,15 +77,15 @@ import java.util.logging.Logger; * * @author iwgeric */ -public class AndroidJoyInputHandler implements JoyInput { - private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName()); +public class AndroidJoyInput implements JoyInput { + private static final Logger logger = Logger.getLogger(AndroidJoyInput.class.getName()); - private List joystickList = new ArrayList(); + protected AndroidInputHandler inputHandler; + protected List joystickList = new ArrayList(); // private boolean dontSendHistory = false; // Internal - private GLSurfaceView view; private boolean initialized = false; private RawInputListener listener = null; private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue(); @@ -96,34 +94,29 @@ public class AndroidJoyInputHandler implements JoyInput { private boolean vibratorActive = false; private long maxRumbleTime = 250; // 250ms - public AndroidJoyInputHandler() { - int buildVersion = Build.VERSION.SDK_INT; - logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); -// if (buildVersion >= 14) { -// touchHandler = new AndroidTouchHandler14(this); -// } else if (buildVersion >= 8){ -// touchHandler = new AndroidTouchHandler(this); -// } + public AndroidJoyInput(AndroidInputHandler inputHandler) { + this.inputHandler = inputHandler; sensorJoyInput = new AndroidSensorJoyInput(this); } public void setView(GLSurfaceView view) { -// if (touchHandler != null) { -// touchHandler.setView(view); -// } + if (view == null) { + vibrator = null; + } else { + // Get instance of Vibrator from current Context + vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null) { + logger.log(Level.FINE, "Vibrator Service not found."); + } + } + if (sensorJoyInput != null) { sensorJoyInput.setView(view); } - this.view = (GLSurfaceView)view; - - } - - public View getView() { - return view; } public void loadSettings(AppSettings settings) { -// sensorEventsEnabled = settings.useSensors(); + } public void addEvent(InputEvent event) { @@ -155,20 +148,8 @@ public class AndroidJoyInputHandler implements JoyInput { } - - - - @Override public void initialize() { -// if (sensorJoyInput != null) { -// sensorJoyInput.initialize(); -// } - // Get instance of Vibrator from current Context - vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator == null) { - logger.log(Level.FINE, "Vibrator Service not found."); - } initialized = true; } @@ -211,8 +192,8 @@ public class AndroidJoyInputHandler implements JoyInput { }; final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from - logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", - new Object[]{amount, rumbleOnDur, rumbleOffDur}); +// logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", +// new Object[]{amount, rumbleOnDur, rumbleOffDur}); if (rumbleOnDur > 0) { vibrator.vibrate(rumblePattern, rumbleRepeatFrom); @@ -226,9 +207,8 @@ public class AndroidJoyInputHandler implements JoyInput { @Override public Joystick[] loadJoysticks(InputManager inputManager) { + logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName()); joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); - - return joystickList.toArray( new Joystick[joystickList.size()] ); } @@ -252,6 +232,4 @@ public class AndroidJoyInputHandler implements JoyInput { } - - } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java new file mode 100644 index 000000000..00478aea1 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java @@ -0,0 +1,108 @@ +/* + * 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.input.android; + +import android.view.KeyEvent; +import android.view.MotionEvent; +import com.jme3.input.InputManager; +import com.jme3.input.Joystick; +import java.util.logging.Logger; + +/** + * AndroidJoyInput14 extends AndroidJoyInput + * to include support for physical joysticks/gamepads.
+ * + * @author iwgeric + */ +public class AndroidJoyInput14 extends AndroidJoyInput { + private static final Logger logger = Logger.getLogger(AndroidJoyInput14.class.getName()); + + private AndroidJoystickJoyInput14 joystickJoyInput; + + public AndroidJoyInput14(AndroidInputHandler inputHandler) { + super(inputHandler); + joystickJoyInput = new AndroidJoystickJoyInput14(this); + } + + /** + * Pauses the joystick device listeners to save battery life if they are not needed. + * Used to pause when the activity pauses + */ + @Override + public void pauseJoysticks() { + super.pauseJoysticks(); + + if (joystickJoyInput != null) { + joystickJoyInput.pauseJoysticks(); + } + } + + /** + * Resumes the joystick device listeners. + * Used to resume when the activity comes to the top of the stack + */ + @Override + public void resumeJoysticks() { + super.resumeJoysticks(); + if (joystickJoyInput != null) { + joystickJoyInput.resumeJoysticks(); + } + + } + + @Override + public void destroy() { + super.destroy(); + if (joystickJoyInput != null) { + joystickJoyInput.destroy(); + } + } + + @Override + public Joystick[] loadJoysticks(InputManager inputManager) { + // load the simulated joystick for device orientation + super.loadJoysticks(inputManager); + // load physical gamepads/joysticks + joystickList.addAll(joystickJoyInput.loadJoysticks(joystickList.size(), inputManager)); + // return the list of joysticks back to InputManager + return joystickList.toArray( new Joystick[joystickList.size()] ); + } + + public boolean onGenericMotion(MotionEvent event) { + return joystickJoyInput.onGenericMotion(event); + } + + public boolean onKey(KeyEvent event) { + return joystickJoyInput.onKey(event); + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java new file mode 100644 index 000000000..370db4f10 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java @@ -0,0 +1,403 @@ +/* + * 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.input.android; + +import android.view.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +import com.jme3.input.AbstractJoystick; +import com.jme3.input.DefaultJoystickAxis; +import com.jme3.input.DefaultJoystickButton; +import com.jme3.input.InputManager; +import com.jme3.input.JoyInput; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickAxis; +import com.jme3.input.JoystickButton; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Main class that creates and manages Android inputs for physical gamepads/joysticks. + * + * @author iwgeric + */ +public class AndroidJoystickJoyInput14 { + private static final Logger logger = Logger.getLogger(AndroidJoystickJoyInput14.class.getName()); + + private boolean loaded = false; + private AndroidJoyInput joyInput; + private Map joystickIndex = new HashMap(); + + private static int[] AndroidGamepadButtons = { + // Dpad buttons + KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_CENTER, + + // pressing joystick down + KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR, + + // buttons + KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, + + // buttons on back of device + KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_BUTTON_R1, + KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2, + + // start / select buttons + KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_SELECT, + KeyEvent.KEYCODE_BUTTON_MODE, + + }; + + public AndroidJoystickJoyInput14(AndroidJoyInput joyInput) { + this.joyInput = joyInput; + } + + + public void pauseJoysticks() { + + } + + public void resumeJoysticks() { + + } + + public void destroy() { + + } + + public List loadJoysticks(int joyId, InputManager inputManager) { + logger.log(Level.INFO, "loading Joystick devices"); + ArrayList joysticks = new ArrayList(); + joysticks.clear(); + joystickIndex.clear(); + + ArrayList gameControllerDeviceIds = new ArrayList(); + int[] deviceIds = InputDevice.getDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice dev = InputDevice.getDevice(deviceId); + int sources = dev.getSources(); + logger.log(Level.FINE, "deviceId[{0}] sources: {1}", new Object[]{deviceId, sources}); + + // Verify that the device has gamepad buttons, control sticks, or both. + if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { + // This device is a game controller. Store its device ID. + if (!gameControllerDeviceIds.contains(deviceId)) { + gameControllerDeviceIds.add(deviceId); + logger.log(Level.FINE, "Attempting to create joystick for device: {0}", dev); + // Create an AndroidJoystick and store the InputDevice so we + // can later correspond the input from the InputDevice to the + // appropriate jME Joystick event + AndroidJoystick joystick = new AndroidJoystick(inputManager, + joyInput, + dev, + joyId+joysticks.size(), + dev.getName()); + joystickIndex.put(deviceId, joystick); + joysticks.add(joystick); + + // Each analog input is reported as a MotionRange + // The axis number corresponds to the type of axis + // The AndroidJoystick.addAxis(MotionRange) converts the axis + // type reported by Android into the jME Joystick axis + List motionRanges = dev.getMotionRanges(); + for (MotionRange motionRange: motionRanges) { + logger.log(Level.INFO, "motion range: {0}", motionRange.toString()); + logger.log(Level.INFO, "axis: {0}", motionRange.getAxis()); + JoystickAxis axis = joystick.addAxis(motionRange); + logger.log(Level.INFO, "added axis: {0}", axis); + } + + // InputDevice has a method for determining if a keyCode is + // supported (InputDevice public boolean[] hasKeys (int... keys)). + // But this method wasn't added until rev 19 (Android 4.4) + // Therefore, we only can query the entire device and see if + // any InputDevice supports the keyCode. This may result in + // buttons being configured that don't exist on the specific + // device, but I haven't found a better way yet. + for (int keyCode: AndroidGamepadButtons) { + logger.log(Level.INFO, "button[{0}]: {1}", + new Object[]{keyCode, KeyCharacterMap.deviceHasKey(keyCode)}); + if (KeyCharacterMap.deviceHasKey(keyCode)) { + // add button even though we aren't sure if the button + // actually exists on this InputDevice + logger.log(Level.INFO, "button[{0}] exists somewhere", keyCode); + JoystickButton button = joystick.addButton(keyCode); + logger.log(Level.INFO, "added button: {0}", button); + } + } + + } + } + } + + + loaded = true; + return joysticks; + } + + public boolean onGenericMotion(MotionEvent event) { + boolean consumed = false; +// logger.log(Level.INFO, "onGenericMotion event: {0}", event); + event.getDeviceId(); + event.getSource(); +// logger.log(Level.INFO, "deviceId: {0}, source: {1}", new Object[]{event.getDeviceId(), event.getSource()}); + AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); + if (joystick != null) { + for (int androidAxis: joystick.getAndroidAxes()) { + String axisName = MotionEvent.axisToString(androidAxis); + float value = event.getAxisValue(androidAxis); + int action = event.getAction(); + if (action == MotionEvent.ACTION_MOVE) { +// logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}", +// new Object[]{androidAxis, axisName, value}); + JoystickAxis axis = joystick.getAxis(androidAxis); + if (axis != null) { +// logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}, deadzone: {3}", +// new Object[]{androidAxis, axisName, value, axis.getDeadZone()}); + JoyAxisEvent axisEvent = new JoyAxisEvent(axis, value); + joyInput.addEvent(axisEvent); + consumed = true; + } else { +// logger.log(Level.INFO, "axis was null for axisName: {0}", axisName); + } + } else { +// logger.log(Level.INFO, "action: {0}", action); + } + } + } + + return consumed; + } + + public boolean onKey(KeyEvent event) { + boolean consumed = false; +// logger.log(Level.INFO, "onKey event: {0}", event); + + event.getDeviceId(); + event.getSource(); + AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); + if (joystick != null) { + JoystickButton button = joystick.getButton(event.getKeyCode()); + if (button != null) { + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; + JoyButtonEvent buttonEvent = new JoyButtonEvent(button, pressed); + joyInput.addEvent(buttonEvent); + consumed = true; + } + } + + return consumed; + } + + protected class AndroidJoystick extends AbstractJoystick { + + private JoystickAxis nullAxis; + private InputDevice device; + private JoystickAxis xAxis; + private JoystickAxis yAxis; + private JoystickAxis povX; + private JoystickAxis povY; + private Map axisIndex = new HashMap(); + private Map buttonIndex = new HashMap(); + + public AndroidJoystick( InputManager inputManager, JoyInput joyInput, InputDevice device, + int joyId, String name ) { + super( inputManager, joyInput, joyId, name ); + + this.device = device; + + this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1, + "Null", "null", false, false, 0 ); + this.xAxis = nullAxis; + this.yAxis = nullAxis; + this.povX = nullAxis; + this.povY = nullAxis; + } + + protected JoystickAxis getAxis(int androidAxis) { + return axisIndex.get(androidAxis); + } + + protected Set getAndroidAxes() { + return axisIndex.keySet(); + } + + protected JoystickButton getButton(int keyCode) { + return buttonIndex.get(keyCode); + } + + protected JoystickButton addButton( int keyCode ) { + +// logger.log(Level.FINE, "Adding button: {0}", keyCode); + + String name = KeyEvent.keyCodeToString(keyCode); + String logicalId = KeyEvent.keyCodeToString(keyCode); + // A/B/X/Y buttons + if (keyCode == KeyEvent.KEYCODE_BUTTON_Y) { + logicalId = JoystickButton.BUTTON_0; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) { + logicalId = JoystickButton.BUTTON_2; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_B) { + logicalId = JoystickButton.BUTTON_1; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_X) { + logicalId = JoystickButton.BUTTON_3; + // Front buttons Some of these have the top ones and the bottoms ones flipped. + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1) { + logicalId = JoystickButton.BUTTON_4; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1) { + logicalId = JoystickButton.BUTTON_5; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L2) { + logicalId = JoystickButton.BUTTON_6; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R2) { + logicalId = JoystickButton.BUTTON_7; +// // Dpad buttons +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// logicalId = JoystickButton.BUTTON_8; +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// logicalId = JoystickButton.BUTTON_9; +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// logicalId = JoystickButton.BUTTON_8; +// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { +// logicalId = JoystickButton.BUTTON_9; + // Select and start buttons + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) { + logicalId = JoystickButton.BUTTON_8; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_START) { + logicalId = JoystickButton.BUTTON_9; + // Joystick push buttons + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL) { + logicalId = JoystickButton.BUTTON_10; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR) { + logicalId = JoystickButton.BUTTON_11; + } + + JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), + name, logicalId ); + addButton(button); + buttonIndex.put( keyCode, button ); + return button; + } + + protected JoystickAxis addAxis(MotionRange motionRange) { + + String name = MotionEvent.axisToString(motionRange.getAxis()); + + String logicalId = MotionEvent.axisToString(motionRange.getAxis()); + if (motionRange.getAxis() == MotionEvent.AXIS_X) { + logicalId = JoystickAxis.X_AXIS; + } else if (motionRange.getAxis() == MotionEvent.AXIS_Y) { + logicalId = JoystickAxis.Y_AXIS; + } else if (motionRange.getAxis() == MotionEvent.AXIS_Z) { + logicalId = JoystickAxis.Z_AXIS; + } else if (motionRange.getAxis() == MotionEvent.AXIS_RZ) { + logicalId = JoystickAxis.Z_ROTATION; + } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { + logicalId = JoystickAxis.POV_X; + } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { + logicalId = JoystickAxis.POV_Y; + } +// String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); +// if( name != original ) { +// logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); +// } + + JoystickAxis axis = new DefaultJoystickAxis(getInputManager(), + this, + getAxisCount(), + name, + logicalId, + true, + true, + motionRange.getFlat()); + + if (motionRange.getAxis() == MotionEvent.AXIS_X) { + xAxis = axis; + } + if (motionRange.getAxis() == MotionEvent.AXIS_Y) { + yAxis = axis; + } + if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { + povX = axis; + } + if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { + povY = axis; + } + + addAxis(axis); + axisIndex.put(motionRange.getAxis(), axis); + return axis; + } + + @Override + public JoystickAxis getXAxis() { + return xAxis; + } + + @Override + public JoystickAxis getYAxis() { + return yAxis; + } + + @Override + public JoystickAxis getPovXAxis() { + return povX; + } + + @Override + public JoystickAxis getPovYAxis() { + return povY; + } + + @Override + public int getXAxisIndex(){ + return xAxis.getAxisId(); + } + + @Override + public int getYAxisIndex(){ + return yAxis.getAxisId(); + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java deleted file mode 100644 index 997974c31..000000000 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.input.android; - -import android.content.Context; -import android.view.KeyEvent; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import com.jme3.input.event.KeyInputEvent; -import com.jme3.input.event.TouchEvent; -import java.util.logging.Logger; - -/** - * AndroidKeyHandler recieves onKey events from the Android system and creates - * the jME KeyEvents. onKey is used by Android to receive keys from the keyboard - * or device buttons. All key events are consumed by jME except for the Volume - * buttons and menu button. - * - * This class also provides the functionality to display or hide the soft keyboard - * for inputing single key events. Use OGLESContext to display an dialog to type - * in complete strings. - * - * @author iwgeric - */ -public class AndroidKeyHandler implements View.OnKeyListener { - private static final Logger logger = Logger.getLogger(AndroidKeyHandler.class.getName()); - - private AndroidInputHandler androidInput; - private boolean sendKeyEvents = true; - - public AndroidKeyHandler(AndroidInputHandler androidInput) { - this.androidInput = androidInput; - } - - public void initialize() { - } - - public void destroy() { - } - - public void setView(View view) { - if (view != null) { - view.setOnKeyListener(this); - } else { - androidInput.getView().setOnKeyListener(null); - } - } - - /** - * onKey gets called from android thread on key events - */ - public boolean onKey(View view, int keyCode, KeyEvent event) { - if (androidInput.isInitialized() && view != androidInput.getView()) { - return false; - } - - TouchEvent evt; - // TODO: get touch event from pool - if (event.getAction() == KeyEvent.ACTION_DOWN) { - evt = new TouchEvent(); - evt.set(TouchEvent.Type.KEY_DOWN); - evt.setKeyCode(keyCode); - evt.setCharacters(event.getCharacters()); - evt.setTime(event.getEventTime()); - - // Send the event - androidInput.addEvent(evt); - - } else if (event.getAction() == KeyEvent.ACTION_UP) { - evt = new TouchEvent(); - evt.set(TouchEvent.Type.KEY_UP); - evt.setKeyCode(keyCode); - evt.setCharacters(event.getCharacters()); - evt.setTime(event.getEventTime()); - - // Send the event - androidInput.addEvent(evt); - - } - - if (androidInput.isSimulateKeyboard()) { - KeyInputEvent kie; - char unicodeChar = (char)event.getUnicodeChar(); - int jmeKeyCode = AndroidKeyMapping.getJmeKey(keyCode); - - boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; - boolean repeating = pressed && event.getRepeatCount() > 0; - - kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating); - kie.setTime(event.getEventTime()); - androidInput.addEvent(kie); -// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}", -// new Object[]{keyCode, jmeKeyCode, pressed, repeating}); -// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie); - } - - // consume all keys ourself except Volume Up/Down and Menu - // Don't do Menu so that typical Android Menus can be created and used - // by the user in MainActivity - if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || - (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || - (keyCode == KeyEvent.KEYCODE_MENU)) { - return false; - } else { - return true; - } - - } - -} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java index e99d8e9fc..32d1e0008 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java @@ -37,13 +37,14 @@ import java.util.logging.Logger; /** * AndroidKeyMapping is just a utility to convert the Android keyCodes into - * jME KeyCodes received in jME's KeyEvent will match between Desktop and Android. - * + * jME KeyCodes so that events received in jME's KeyEvent will match between + * Desktop and Android. + * * @author iwgeric */ public class AndroidKeyMapping { private static final Logger logger = Logger.getLogger(AndroidKeyMapping.class.getName()); - + private static final int[] ANDROID_TO_JME = { 0x0, // unknown 0x0, // key code soft left @@ -141,9 +142,13 @@ public class AndroidKeyMapping { 0x0,//media fastforward 0x0,//mute }; - + public static int getJmeKey(int androidKey) { - return ANDROID_TO_JME[androidKey]; + if (androidKey > ANDROID_TO_JME.length) { + return androidKey; + } else { + return ANDROID_TO_JME[androidKey]; + } } - + } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java index 823aa3d9d..cdd7e6494 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java @@ -75,15 +75,15 @@ import java.util.logging.Logger; public class AndroidSensorJoyInput implements SensorEventListener { private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName()); - private AndroidJoyInputHandler joyHandler; + private AndroidJoyInput joyInput; private SensorManager sensorManager = null; private WindowManager windowManager = null; private IntMap sensors = new IntMap(); private int lastRotation = 0; private boolean loaded = false; - public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) { - this.joyHandler = joyHandler; + public AndroidSensorJoyInput(AndroidJoyInput joyInput) { + this.joyInput = joyInput; } /** @@ -96,7 +96,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { int sensorAccuracy = -1; float[] lastValues; final Object valuesLock = new Object(); - ArrayList axes = new ArrayList(); + ArrayList axes = new ArrayList(); boolean enabled = false; boolean haveData = false; @@ -306,7 +306,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { */ private boolean updateOrientation() { SensorData sensorData; - AndroidJoystickAxis axis; + AndroidSensorJoystickAxis axis; final float[] curInclinationMat = new float[16]; final float[] curRotationMat = new float[16]; final float[] rotatedRotationMat = new float[16]; @@ -374,7 +374,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { sensorData.haveData = true; } else { if (axis.isChanged()) { - joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); + joyInput.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); } } } @@ -401,10 +401,10 @@ public class AndroidSensorJoyInput implements SensorEventListener { public Joystick loadJoystick(int joyId, InputManager inputManager) { SensorData sensorData; - AndroidJoystickAxis axis; + AndroidSensorJoystickAxis axis; - AndroidJoystick joystick = new AndroidJoystick(inputManager, - joyHandler, + AndroidSensorJoystick joystick = new AndroidSensorJoystick(inputManager, + joyInput, joyId, "AndroidSensorsJoystick"); @@ -522,15 +522,15 @@ public class AndroidSensorJoyInput implements SensorEventListener { if (!loaded) { return; } - logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}", - new Object[]{se.sensor.getName(), se.accuracy, se.values}); +// logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}", +// new Object[]{se.sensor.getName(), se.accuracy, se.values}); int sensorType = se.sensor.getType(); SensorData sensorData = sensors.get(sensorType); if (sensorData != null) { - logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}", - new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE}); +// logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}", +// new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE}); } if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) { @@ -543,8 +543,8 @@ public class AndroidSensorJoyInput implements SensorEventListener { } } - if (sensorData != null && sensorData.axes.size() > 0) { - AndroidJoystickAxis axis; + if (sensorData.axes.size() > 0) { + AndroidSensorJoystickAxis axis; for (int i=0; i lastPositions = new HashMap(); - - protected int numPointers = 0; - - protected AndroidInputHandler androidInput; - protected AndroidGestureHandler gestureHandler; - - public AndroidTouchHandler(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { - this.androidInput = androidInput; - this.gestureHandler = gestureHandler; - } - - public void initialize() { - } - - public void destroy() { - setView(null); - } - - public void setView(View view) { - if (view != null) { - view.setOnTouchListener(this); - } else { - androidInput.getView().setOnTouchListener(null); - } - } - - protected int getPointerIndex(MotionEvent event) { - return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - } - - protected int getPointerId(MotionEvent event) { - return event.getPointerId(getPointerIndex(event)); - } - - protected int getAction(MotionEvent event) { - return event.getAction() & MotionEvent.ACTION_MASK; - } - - /** - * onTouch gets called from android thread on touch events - */ - public boolean onTouch(View view, MotionEvent event) { - if (!androidInput.isInitialized() || view != androidInput.getView()) { - return false; - } - - boolean bWasHandled = false; - TouchEvent touch = null; - // System.out.println("native : " + event.getAction()); - int action = getAction(event); - int pointerIndex = getPointerIndex(event); - int pointerId = getPointerId(event); - Vector2f lastPos = lastPositions.get(pointerId); - float jmeX; - float jmeY; - - numPointers = event.getPointerCount(); - - // final int historySize = event.getHistorySize(); - //final int pointerCount = event.getPointerCount(); - switch (getAction(event)) { - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_DOWN: - jmeX = androidInput.getJmeX(event.getX(pointerIndex)); - jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); - touch = androidInput.getFreeTouchEvent(); - touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - - lastPos = new Vector2f(jmeX, jmeY); - lastPositions.put(pointerId, lastPos); - - processEvent(touch); - - bWasHandled = true; - break; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - jmeX = androidInput.getJmeX(event.getX(pointerIndex)); - jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); - touch = androidInput.getFreeTouchEvent(); - touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - lastPositions.remove(pointerId); - - processEvent(touch); - - bWasHandled = true; - break; - case MotionEvent.ACTION_MOVE: - // Convert all pointers into events - for (int p = 0; p < event.getPointerCount(); p++) { - jmeX = androidInput.getJmeX(event.getX(p)); - jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p))); - lastPos = lastPositions.get(event.getPointerId(p)); - if (lastPos == null) { - lastPos = new Vector2f(jmeX, jmeY); - lastPositions.put(event.getPointerId(p), lastPos); - } - - float dX = jmeX - lastPos.x; - float dY = jmeY - lastPos.y; - if (dX != 0 || dY != 0) { - touch = androidInput.getFreeTouchEvent(); - touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY); - touch.setPointerId(event.getPointerId(p)); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(p)); - lastPos.set(jmeX, jmeY); - - processEvent(touch); - - bWasHandled = true; - } - } - break; - case MotionEvent.ACTION_OUTSIDE: - break; - - } - - // Try to detect gestures - if (gestureHandler != null) { - gestureHandler.detectGesture(event); - } - - return bWasHandled; - } - - protected void processEvent(TouchEvent event) { - // Add the touch event - androidInput.addEvent(event); - // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events - if (androidInput.isSimulateMouse() && numPointers == 1) { - InputEvent mouseEvent = generateMouseEvent(event); - if (mouseEvent != null) { - // Add the mouse event - androidInput.addEvent(mouseEvent); - } - } - - } - - // TODO: Ring Buffer for mouse events? - protected InputEvent generateMouseEvent(TouchEvent event) { - InputEvent inputEvent = null; - int newX; - int newY; - int newDX; - int newDY; - - if (androidInput.isMouseEventsInvertX()) { - newX = (int) (androidInput.invertX(event.getX())); - newDX = (int)event.getDeltaX() * -1; - } else { - newX = (int) event.getX(); - newDX = (int)event.getDeltaX(); - } - - if (androidInput.isMouseEventsInvertY()) { - newY = (int) (androidInput.invertY(event.getY())); - newDY = (int)event.getDeltaY() * -1; - } else { - newY = (int) event.getY(); - newDY = (int)event.getDeltaY(); - } - - switch (event.getType()) { - case DOWN: - // Handle mouse down event - inputEvent = new MouseButtonEvent(0, true, newX, newY); - inputEvent.setTime(event.getTime()); - break; - - case UP: - // Handle mouse up event - inputEvent = new MouseButtonEvent(0, false, newX, newY); - inputEvent.setTime(event.getTime()); - break; - - case HOVER_MOVE: - case MOVE: - inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); - inputEvent.setTime(event.getTime()); - break; - } - - return inputEvent; - } - -} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java new file mode 100644 index 000000000..bddc5fab3 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import static com.jme3.input.event.TouchEvent.Type.DOWN; +import static com.jme3.input.event.TouchEvent.Type.MOVE; +import static com.jme3.input.event.TouchEvent.Type.UP; +import com.jme3.math.Vector2f; +import com.jme3.system.AppSettings; +import java.util.HashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidTouchInput is the base class that receives touch inputs from the + * Android system and creates the TouchEvents for jME. This class is designed + * to handle the base touch events for Android rev 9 (Android 2.3). This is + * extended by other classes to add features that were introducted after + * Android rev 9. + * + * @author iwgeric + */ +public class AndroidTouchInput implements TouchInput { + private static final Logger logger = Logger.getLogger(AndroidTouchInput.class.getName()); + + private boolean mouseEventsEnabled = true; + private boolean mouseEventsInvertX = false; + private boolean mouseEventsInvertY = false; + private boolean keyboardEventsEnabled = false; + private boolean dontSendHistory = false; + + protected int numPointers = 0; + final private HashMap lastPositions = new HashMap(); + final private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + private final static int MAX_TOUCH_EVENTS = 1024; + private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); + private float scaleX = 1f; + private float scaleY = 1f; + + private boolean initialized = false; + private RawInputListener listener = null; + + private GestureDetector gestureDetector; + private ScaleGestureDetector scaleDetector; + + protected AndroidInputHandler androidInput; + + public AndroidTouchInput(AndroidInputHandler androidInput) { + this.androidInput = androidInput; + } + + public GestureDetector getGestureDetector() { + return gestureDetector; + } + + public void setGestureDetector(GestureDetector gestureDetector) { + this.gestureDetector = gestureDetector; + } + + public ScaleGestureDetector getScaleDetector() { + return scaleDetector; + } + + public void setScaleDetector(ScaleGestureDetector scaleDetector) { + this.scaleDetector = scaleDetector; + } + + public float invertX(float origX) { + return getJmeX(androidInput.getView().getWidth()) - origX; + } + + public float invertY(float origY) { + return getJmeY(androidInput.getView().getHeight()) - origY; + } + + public float getJmeX(float origX) { + return origX * scaleX; + } + + public float getJmeY(float origY) { + return origY * scaleY; + } + + public void loadSettings(AppSettings settings) { + keyboardEventsEnabled = settings.isEmulateKeyboard(); + mouseEventsEnabled = settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + + // view width and height are 0 until the view is displayed on the screen + if (androidInput.getView().getWidth() != 0 && androidInput.getView().getHeight() != 0) { + scaleX = (float)settings.getWidth() / (float)androidInput.getView().getWidth(); + scaleY = (float)settings.getHeight() / (float)androidInput.getView().getHeight(); + } + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", + new Object[]{scaleX, scaleY}); + + + } + + + protected int getPointerIndex(MotionEvent event) { + return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + + protected int getPointerId(MotionEvent event) { + return event.getPointerId(getPointerIndex(event)); + } + + protected int getAction(MotionEvent event) { + return event.getAction() & MotionEvent.ACTION_MASK; + } + + public boolean onTouch(MotionEvent event) { + if (!isInitialized()) { + return false; + } + + boolean bWasHandled = false; + TouchEvent touch = null; + // System.out.println("native : " + event.getAction()); + int action = getAction(event); + int pointerIndex = getPointerIndex(event); + int pointerId = getPointerId(event); + Vector2f lastPos = lastPositions.get(pointerId); + float jmeX; + float jmeY; + + numPointers = event.getPointerCount(); + + // final int historySize = event.getHistorySize(); + //final int pointerCount = event.getPointerCount(); + switch (getAction(event)) { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touch = getFreeTouchEvent(); + touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + + lastPos = new Vector2f(jmeX, jmeY); + lastPositions.put(pointerId, lastPos); + + addEvent(touch); + addEvent(generateMouseEvent(touch)); + + bWasHandled = true; + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touch = getFreeTouchEvent(); + touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + lastPositions.remove(pointerId); + + addEvent(touch); + addEvent(generateMouseEvent(touch)); + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + jmeX = getJmeX(event.getX(p)); + jmeY = invertY(getJmeY(event.getY(p))); + lastPos = lastPositions.get(event.getPointerId(p)); + if (lastPos == null) { + lastPos = new Vector2f(jmeX, jmeY); + lastPositions.put(event.getPointerId(p), lastPos); + } + + float dX = jmeX - lastPos.x; + float dY = jmeY - lastPos.y; + if (dX != 0 || dY != 0) { + touch = getFreeTouchEvent(); + touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(p)); + lastPos.set(jmeX, jmeY); + + addEvent(touch); + addEvent(generateMouseEvent(touch)); + + bWasHandled = true; + } + } + break; + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + if (gestureDetector != null) { + gestureDetector.onTouchEvent(event); + } + if (scaleDetector != null) { + scaleDetector.onTouchEvent(event); + } + + return bWasHandled; + } + + // TODO: Ring Buffer for mouse events? + public InputEvent generateMouseEvent(TouchEvent event) { + InputEvent inputEvent = null; + int newX; + int newY; + int newDX; + int newDY; + + // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events + if (!isSimulateMouse() || numPointers > 1) { + return null; + } + + + if (isMouseEventsInvertX()) { + newX = (int) (invertX(event.getX())); + newDX = (int)event.getDeltaX() * -1; + } else { + newX = (int) event.getX(); + newDX = (int)event.getDeltaX(); + } + + if (isMouseEventsInvertY()) { + newY = (int) (invertY(event.getY())); + newDY = (int)event.getDeltaY() * -1; + } else { + newY = (int) event.getY(); + newDY = (int)event.getDeltaY(); + } + + switch (event.getType()) { + case DOWN: + // Handle mouse down event + inputEvent = new MouseButtonEvent(0, true, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case UP: + // Handle mouse up event + inputEvent = new MouseButtonEvent(0, false, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case HOVER_MOVE: + case MOVE: + inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); + inputEvent.setTime(event.getTime()); + break; + } + + return inputEvent; + } + + + public boolean onKey(KeyEvent event) { + if (!isInitialized()) { + return false; + } + + TouchEvent evt; + // TODO: get touch event from pool + if (event.getAction() == KeyEvent.ACTION_DOWN) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(event.getKeyCode()); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + addEvent(evt); + + } else if (event.getAction() == KeyEvent.ACTION_UP) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(event.getKeyCode()); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + addEvent(evt); + + } + + if (isSimulateKeyboard()) { + KeyInputEvent kie; + char unicodeChar = (char)event.getUnicodeChar(); + int jmeKeyCode = AndroidKeyMapping.getJmeKey(event.getKeyCode()); + + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; + boolean repeating = pressed && event.getRepeatCount() > 0; + + kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating); + kie.setTime(event.getEventTime()); + addEvent(kie); +// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}", +// new Object[]{event.getKeyCode(), jmeKeyCode, pressed, repeating}); +// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie); + } + + // consume all keys ourself except Volume Up/Down and Menu + // Don't do Menu so that typical Android Menus can be created and used + // by the user in MainActivity + if ((event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) || + (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) || + (event.getKeyCode() == KeyEvent.KEYCODE_MENU)) { + return false; + } else { + return true; + } + + } + + + + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + touchEventPool.initialize(); + + initialized = true; + } + + @Override + public void destroy() { + initialized = false; + + touchEventPool.destroy(); + + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + + @Override + public void update() { + if (listener != null) { + InputEvent inputEvent; + + while ((inputEvent = inputEventQueue.poll()) != null) { + if (inputEvent instanceof TouchEvent) { + listener.onTouchEvent((TouchEvent)inputEvent); + } else if (inputEvent instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent)inputEvent); + } else if (inputEvent instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent)inputEvent); + } else if (inputEvent instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent)inputEvent); + } + } + } + } + + // ----------------------------------------- + + public TouchEvent getFreeTouchEvent() { + return touchEventPool.getNextFreeEvent(); + } + + public void addEvent(InputEvent event) { + if (event == null) { + return; + } + + logger.log(Level.INFO, "event: {0}", event); + + inputEventQueue.add(event); + if (event instanceof TouchEvent) { + touchEventPool.storeEvent((TouchEvent)event); + } + + } + + @Override + public void setSimulateMouse(boolean simulate) { + this.mouseEventsEnabled = simulate; + } + + @Override + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + @Override + public void setSimulateKeyboard(boolean simulate) { + this.keyboardEventsEnabled = simulate; + } + + @Override + public boolean isSimulateKeyboard() { + return keyboardEventsEnabled; + } + + @Override + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java similarity index 67% rename from jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java rename to jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java index 1a785a5e9..617a4b719 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java @@ -33,7 +33,6 @@ package com.jme3.input.android; import android.view.MotionEvent; -import android.view.View; import com.jme3.input.event.TouchEvent; import com.jme3.math.Vector2f; import java.util.HashMap; @@ -41,36 +40,20 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the - * Android touch event functionality between Android rev 9 (Android 2.3) and - * Android rev 14 (Android 4.0). - * + * AndroidTouchHandler14 extends AndroidTouchHandler to process the onHover + * events added in Android rev 14 (Android 4.0). + * * @author iwgeric */ -public class AndroidTouchHandler14 extends AndroidTouchHandler implements - View.OnHoverListener { - private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName()); +public class AndroidTouchInput14 extends AndroidTouchInput { + private static final Logger logger = Logger.getLogger(AndroidTouchInput14.class.getName()); final private HashMap lastHoverPositions = new HashMap(); - - public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { - super(androidInput, gestureHandler); - } - @Override - public void setView(View view) { - if (view != null) { - view.setOnHoverListener(this); - } else { - androidInput.getView().setOnHoverListener(null); - } - super.setView(view); + public AndroidTouchInput14(AndroidInputHandler androidInput) { + super(androidInput); } - - public boolean onHover(View view, MotionEvent event) { - if (view == null || view != androidInput.getView()) { - return false; - } - + + public boolean onHover(MotionEvent event) { boolean consumed = false; int action = getAction(event); int pointerId = getPointerId(event); @@ -78,34 +61,34 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements Vector2f lastPos = lastHoverPositions.get(pointerId); float jmeX; float jmeY; - + numPointers = event.getPointerCount(); - - logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}", - new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()}); + +// logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}", +// new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()}); TouchEvent touchEvent; switch (action) { case MotionEvent.ACTION_HOVER_ENTER: - jmeX = androidInput.getJmeX(event.getX(pointerIndex)); - jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); - touchEvent = androidInput.getFreeTouchEvent(); + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touchEvent = getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.HOVER_START, jmeX, jmeY, 0, 0); touchEvent.setPointerId(pointerId); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure(pointerIndex)); - + lastPos = new Vector2f(jmeX, jmeY); lastHoverPositions.put(pointerId, lastPos); - - processEvent(touchEvent); + + addEvent(touchEvent); consumed = true; break; case MotionEvent.ACTION_HOVER_MOVE: // Convert all pointers into events for (int p = 0; p < event.getPointerCount(); p++) { - jmeX = androidInput.getJmeX(event.getX(p)); - jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p))); + jmeX = getJmeX(event.getX(p)); + jmeY = invertY(getJmeY(event.getY(p))); lastPos = lastHoverPositions.get(event.getPointerId(p)); if (lastPos == null) { lastPos = new Vector2f(jmeX, jmeY); @@ -115,38 +98,39 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements float dX = jmeX - lastPos.x; float dY = jmeY - lastPos.y; if (dX != 0 || dY != 0) { - touchEvent = androidInput.getFreeTouchEvent(); + touchEvent = getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.HOVER_MOVE, jmeX, jmeY, dX, dY); touchEvent.setPointerId(event.getPointerId(p)); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure(p)); lastPos.set(jmeX, jmeY); - processEvent(touchEvent); + addEvent(touchEvent); } } consumed = true; break; case MotionEvent.ACTION_HOVER_EXIT: - jmeX = androidInput.getJmeX(event.getX(pointerIndex)); - jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); - touchEvent = androidInput.getFreeTouchEvent(); + jmeX = getJmeX(event.getX(pointerIndex)); + jmeY = invertY(getJmeY(event.getY(pointerIndex))); + touchEvent = getFreeTouchEvent(); touchEvent.set(TouchEvent.Type.HOVER_END, jmeX, jmeY, 0, 0); touchEvent.setPointerId(pointerId); touchEvent.setTime(event.getEventTime()); touchEvent.setPressure(event.getPressure(pointerIndex)); lastHoverPositions.remove(pointerId); - processEvent(touchEvent); + addEvent(touchEvent); consumed = true; break; default: consumed = false; break; } - + return consumed; + } - + } 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 5ef866188..9472bff0b 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 @@ -47,7 +47,7 @@ import android.widget.EditText; import android.widget.FrameLayout; import com.jme3.input.*; import com.jme3.input.android.AndroidInputHandler; -import com.jme3.input.android.AndroidJoyInputHandler; +import com.jme3.input.android.AndroidInputHandler14; import com.jme3.input.controls.SoftTextDialogInputListener; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; @@ -75,7 +75,6 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInputHandler androidInput; - protected AndroidJoyInputHandler androidJoyInput = null; protected long minFrameDuration = 0; // No FPS cap protected long lastUpdateTime = 0; @@ -111,18 +110,17 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex // Start to set up the view GLSurfaceView view = new GLSurfaceView(context); + logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT); if (androidInput == null) { - androidInput = new AndroidInputHandler(); + if (Build.VERSION.SDK_INT >= 14) { + androidInput = new AndroidInputHandler14(); + } else if (Build.VERSION.SDK_INT >= 9){ + androidInput = new AndroidInputHandler(); + } } androidInput.setView(view); androidInput.loadSettings(settings); - if (androidJoyInput == null) { - androidJoyInput = new AndroidJoyInputHandler(); - } - androidJoyInput.setView(view); - androidJoyInput.loadSettings(settings); - // setEGLContextClientVersion must be set before calling setRenderer // this means it cannot be set in AndroidConfigChooser (too late) view.setEGLContextClientVersion(2); @@ -235,9 +233,6 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex if (androidInput != null) { androidInput.loadSettings(settings); } - if (androidJoyInput != null) { - androidJoyInput.loadSettings(settings); - } if (settings.getFrameRate() > 0) { minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms @@ -274,12 +269,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex @Override public JoyInput getJoyInput() { - return androidJoyInput; + return androidInput.getJoyInput(); } @Override public TouchInput getTouchInput() { - return androidInput; + return androidInput.getTouchInput(); } @Override From 6c24390663873ef2a434443a6c7493da14d18305 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Fri, 17 Apr 2015 19:53:55 -0400 Subject: [PATCH 073/225] Android: Fix issue handling key events that are tied to both keyboard and gamepad --- .../java/com/jme3/input/android/AndroidInputHandler14.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java index 114bb98fd..4b39ed24c 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java @@ -148,7 +148,8 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O if (isJoystick && joyInput != null) { // logger.log(Level.INFO, "onKey source: {0}, isJoystick: {1}", // new Object[]{source, isJoystick}); - consumed = consumed || ((AndroidJoyInput14)joyInput).onKey(event); + // use inclusive OR to make sure the onKey method is called. + consumed = consumed | ((AndroidJoyInput14)joyInput).onKey(event); } return consumed; From d80de0cc5b74f5c8e8a4f77c80a361708d3bf7fa Mon Sep 17 00:00:00 2001 From: iwgeric Date: Sat, 18 Apr 2015 14:48:30 -0400 Subject: [PATCH 074/225] Android: Add static property to disable sensors but still have joysticks. May move this to AppSettings later, but not sure yet. --- .../main/java/com/jme3/input/android/AndroidJoyInput.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java index e31504520..1e610d24e 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java @@ -79,6 +79,7 @@ import java.util.logging.Logger; */ public class AndroidJoyInput implements JoyInput { private static final Logger logger = Logger.getLogger(AndroidJoyInput.class.getName()); + public static boolean disableSensors = false; protected AndroidInputHandler inputHandler; protected List joystickList = new ArrayList(); @@ -208,7 +209,9 @@ public class AndroidJoyInput implements JoyInput { @Override public Joystick[] loadJoysticks(InputManager inputManager) { logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName()); - joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); + if (!disableSensors) { + joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); + } return joystickList.toArray( new Joystick[joystickList.size()] ); } From fecfa8ccd07bbf1b8731387d4cb89fe1ed2806f7 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Sun, 19 Apr 2015 12:38:43 -0400 Subject: [PATCH 075/225] Android: add joystick compatibility mapping for XBOX 360 controller connected to Android device using USB dongle --- .../resources/joystick-mapping.properties | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index 5f29e4105..d7ce2f50a 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/jme3-core/src/main/resources/joystick-mapping.properties @@ -6,7 +6,7 @@ # the new name as it will be reported through the Joystick # interface. # -# Keys with spaces in them should have those spaces escaped. +# Keys with spaces in them should have those spaces escaped. # Values do not need their spaces escaped. For example: # # Some\ Joystick.0=3 @@ -37,22 +37,29 @@ Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).ry=rz # requires custom code to support trigger buttons but this # keeps it from confusing the .rx mapping. Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).z=trigger - + # Xbox 360 Controller (copied from wireless version) Controller\ (XBOX\ 360\ For\ Windows).0=2 Controller\ (XBOX\ 360\ For\ Windows).1=1 Controller\ (XBOX\ 360\ For\ Windows).2=3 Controller\ (XBOX\ 360\ For\ Windows).3=0 - + Controller\ (XBOX\ 360\ For\ Windows).6=8 Controller\ (XBOX\ 360\ For\ Windows).7=9 - + Controller\ (XBOX\ 360\ For\ Windows).8=10 Controller\ (XBOX\ 360\ For\ Windows).9=11 - + Controller\ (XBOX\ 360\ For\ Windows).rx=z Controller\ (XBOX\ 360\ For\ Windows).ry=rz - + # requires custom code to support trigger buttons but this # keeps it from confusing the .rx mapping. Controller\ (XBOX\ 360\ For\ Windows).z=trigger + +# XBOX 360 Controller connected to Android using +# the USB dongle +Xbox\ 360\ Wireless\ Receiver.AXIS_RX=z +Xbox\ 360\ Wireless\ Receiver.AXIS_RY=rz +Xbox\ 360\ Wireless\ Receiver.z=AXIS_RX +Xbox\ 360\ Wireless\ Receiver.rz=AXIS_RY From 116adbba1f5844a17c38a0e90bb36e58e153c95c Mon Sep 17 00:00:00 2001 From: iwgeric Date: Sun, 19 Apr 2015 12:40:17 -0400 Subject: [PATCH 076/225] Android: Support JoystickCompatibilityMappings for reassigning joystick axes and buttons. Also added ability to add new buttons as events come in due to Android not providing a definitive way to determine which buttons are supported on the device. --- .../android/AndroidJoystickJoyInput14.java | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java index 370db4f10..1ed98977c 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java @@ -44,6 +44,7 @@ import com.jme3.input.JoyInput; import com.jme3.input.Joystick; import com.jme3.input.JoystickAxis; import com.jme3.input.JoystickButton; +import com.jme3.input.JoystickCompatibilityMappings; import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyButtonEvent; import java.util.ArrayList; @@ -220,11 +221,16 @@ public class AndroidJoystickJoyInput14 { AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); if (joystick != null) { JoystickButton button = joystick.getButton(event.getKeyCode()); + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; if (button != null) { - boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; JoyButtonEvent buttonEvent = new JoyButtonEvent(button, pressed); joyInput.addEvent(buttonEvent); consumed = true; + } else { + JoystickButton newButton = joystick.addButton(event.getKeyCode()); + JoyButtonEvent buttonEvent = new JoyButtonEvent(newButton, pressed); + joyInput.addEvent(buttonEvent); + consumed = true; } } @@ -273,44 +279,50 @@ public class AndroidJoystickJoyInput14 { // logger.log(Level.FINE, "Adding button: {0}", keyCode); String name = KeyEvent.keyCodeToString(keyCode); - String logicalId = KeyEvent.keyCodeToString(keyCode); + String original = KeyEvent.keyCodeToString(keyCode); // A/B/X/Y buttons if (keyCode == KeyEvent.KEYCODE_BUTTON_Y) { - logicalId = JoystickButton.BUTTON_0; - } else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) { - logicalId = JoystickButton.BUTTON_2; + original = JoystickButton.BUTTON_0; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_B) { - logicalId = JoystickButton.BUTTON_1; + original = JoystickButton.BUTTON_1; + } else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) { + original = JoystickButton.BUTTON_2; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_X) { - logicalId = JoystickButton.BUTTON_3; + original = JoystickButton.BUTTON_3; // Front buttons Some of these have the top ones and the bottoms ones flipped. } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1) { - logicalId = JoystickButton.BUTTON_4; + original = JoystickButton.BUTTON_4; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1) { - logicalId = JoystickButton.BUTTON_5; + original = JoystickButton.BUTTON_5; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L2) { - logicalId = JoystickButton.BUTTON_6; + original = JoystickButton.BUTTON_6; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R2) { - logicalId = JoystickButton.BUTTON_7; + original = JoystickButton.BUTTON_7; // // Dpad buttons // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { -// logicalId = JoystickButton.BUTTON_8; +// original = JoystickButton.BUTTON_8; // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { -// logicalId = JoystickButton.BUTTON_9; +// original = JoystickButton.BUTTON_9; // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { -// logicalId = JoystickButton.BUTTON_8; +// original = JoystickButton.BUTTON_8; // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { -// logicalId = JoystickButton.BUTTON_9; +// original = JoystickButton.BUTTON_9; // Select and start buttons } else if (keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) { - logicalId = JoystickButton.BUTTON_8; + original = JoystickButton.BUTTON_8; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_START) { - logicalId = JoystickButton.BUTTON_9; + original = JoystickButton.BUTTON_9; // Joystick push buttons } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL) { - logicalId = JoystickButton.BUTTON_10; + original = JoystickButton.BUTTON_10; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR) { - logicalId = JoystickButton.BUTTON_11; + original = JoystickButton.BUTTON_11; + } + + String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); + if( logicalId == null ? original != null : !logicalId.equals(original) ) { + logger.log(Level.FINE, "Remapped: {0} to: {1}", + new Object[]{original, logicalId}); } JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), @@ -324,24 +336,25 @@ public class AndroidJoystickJoyInput14 { String name = MotionEvent.axisToString(motionRange.getAxis()); - String logicalId = MotionEvent.axisToString(motionRange.getAxis()); + String original = MotionEvent.axisToString(motionRange.getAxis()); if (motionRange.getAxis() == MotionEvent.AXIS_X) { - logicalId = JoystickAxis.X_AXIS; + original = JoystickAxis.X_AXIS; } else if (motionRange.getAxis() == MotionEvent.AXIS_Y) { - logicalId = JoystickAxis.Y_AXIS; + original = JoystickAxis.Y_AXIS; } else if (motionRange.getAxis() == MotionEvent.AXIS_Z) { - logicalId = JoystickAxis.Z_AXIS; + original = JoystickAxis.Z_AXIS; } else if (motionRange.getAxis() == MotionEvent.AXIS_RZ) { - logicalId = JoystickAxis.Z_ROTATION; + original = JoystickAxis.Z_ROTATION; } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { - logicalId = JoystickAxis.POV_X; + original = JoystickAxis.POV_X; } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { - logicalId = JoystickAxis.POV_Y; + original = JoystickAxis.POV_Y; + } + String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); + if( logicalId == null ? original != null : !logicalId.equals(original) ) { + logger.log(Level.FINE, "Remapped: {0} to: {1}", + new Object[]{original, logicalId}); } -// String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); -// if( name != original ) { -// logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); -// } JoystickAxis axis = new DefaultJoystickAxis(getInputManager(), this, From ba8349a0cfaa7ec08bb1bcbbc9bb109756d0dfce Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sun, 12 Apr 2015 15:32:41 -0400 Subject: [PATCH 077/225] FBX: ContentTextureKey to disable cache FBXDump remove useless import --- .../com/jme3/scene/plugins/fbx/ContentTextureKey.java | 9 +++++++++ .../java/com/jme3/scene/plugins/fbx/file/FBXDump.java | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) 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/file/FBXDump.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXDump.java index f309a5052..718a18b0a 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,6 @@ import java.lang.reflect.Array; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; -import static org.omg.IOP.IORHelper.id; /** * Quick n' dirty dumper of FBX binary files. From 0a5b68983e0631d32d30aa83413257e1f1c84d82 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 19 Apr 2015 13:33:43 -0400 Subject: [PATCH 078/225] FBX: Rename FBX -> Fbx. Support FBX 6.x IDs in FbxDump. --- .../jme3/scene/plugins/fbx/SceneLoader.java | 84 +++++------ .../fbx/file/{FBXDump.java => FbxDump.java} | 0 .../file/{FBXElement.java => FbxElement.java} | 0 .../fbx/file/{FBXFile.java => FbxFile.java} | 0 .../jme3/scene/plugins/fbx/file/FbxId.java | 130 ++++++++++++++++++ .../file/{FBXReader.java => FbxReader.java} | 0 6 files changed, 172 insertions(+), 42 deletions(-) rename jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/{FBXDump.java => FbxDump.java} (100%) rename jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/{FBXElement.java => FbxElement.java} (100%) rename jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/{FBXFile.java => FbxFile.java} (100%) create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxId.java rename jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/{FBXReader.java => FbxReader.java} (100%) 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/file/FBXDump.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java similarity index 100% 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 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 similarity index 100% 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/file/FbxElement.java 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 100% 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 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 100% 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 From 6f29772862ff7644b7dcddb5a458ac8bebca214b Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 19 Apr 2015 13:42:25 -0400 Subject: [PATCH 079/225] FBX: fix build error due to rename --- .../jme3/scene/plugins/fbx/file/FbxDump.java | 75 +++++++++++-------- .../scene/plugins/fbx/file/FbxElement.java | 70 +++++++++++++++-- .../jme3/scene/plugins/fbx/file/FbxFile.java | 8 +- .../scene/plugins/fbx/file/FbxReader.java | 22 +++--- 4 files changed, 126 insertions(+), 49 deletions(-) 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 index 718a18b0a..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,6 +37,8 @@ import java.lang.reflect.Array; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Quick n' dirty dumper of FBX binary files. @@ -45,12 +47,14 @@ import java.util.Map; * * @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. @@ -58,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); } } } @@ -79,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); } /** @@ -89,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); } } @@ -112,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 @@ -124,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': @@ -158,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) + "\""); @@ -177,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(","); } @@ -188,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(", "); } @@ -214,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 index eeeee872c..1a4d09d53 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/file/FbxElement.java @@ -34,7 +34,7 @@ package com.jme3.scene.plugins.fbx.file; import java.util.ArrayList; import java.util.List; -public class FBXElement { +public class FbxElement { public String id; public List properties; @@ -55,10 +55,70 @@ public class FBXElement { * c - array of unsigned bytes (represented as array of ints) */ public char[] propertiesTypes; - public List children = new ArrayList(); + public List children = new ArrayList(); - public FBXElement(int propsCount) { - properties = new ArrayList(propsCount); - propertiesTypes = new char[propsCount]; + 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 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/FbxReader.java b/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) { From ed2be5e54208d8b8fc21987804f025046e950493 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 19 Apr 2015 18:08:28 -0400 Subject: [PATCH 080/225] FBX: new FBX importer (not yet enabled - old importer still used by default) Still needs work: * Skeletal animation (many issues with transform hierarchies) * N-gons triangulation (only quads supported at the moment) * Light & Camera importing * Z-up to Y-up correction * Morph animation --- .../com/jme3/scene/plugins/fbx/FbxLoader.java | 413 ++++++++++++ .../scene/plugins/fbx/anim/FbxAnimCurve.java | 144 ++++ .../plugins/fbx/anim/FbxAnimCurveNode.java | 147 +++++ .../scene/plugins/fbx/anim/FbxAnimLayer.java | 82 +++ .../scene/plugins/fbx/anim/FbxAnimStack.java | 111 ++++ .../scene/plugins/fbx/anim/FbxAnimUtil.java | 44 ++ .../scene/plugins/fbx/anim/FbxBindPose.java | 103 +++ .../scene/plugins/fbx/anim/FbxCluster.java | 98 +++ .../scene/plugins/fbx/anim/FbxLimbNode.java | 94 +++ .../plugins/fbx/anim/FbxSkinDeformer.java | 66 ++ .../scene/plugins/fbx/anim/FbxToJmeTrack.java | 202 ++++++ .../scene/plugins/fbx/material/FbxImage.java | 190 ++++++ .../plugins/fbx/material/FbxMaterial.java | 363 +++++++++++ .../fbx/material/FbxMaterialProperties.java | 234 +++++++ .../plugins/fbx/material/FbxTexture.java | 146 +++++ .../jme3/scene/plugins/fbx/mesh/FbxLayer.java | 107 +++ .../plugins/fbx/mesh/FbxLayerElement.java | 243 +++++++ .../jme3/scene/plugins/fbx/mesh/FbxMesh.java | 316 +++++++++ .../scene/plugins/fbx/mesh/FbxMeshUtil.java | 69 ++ .../scene/plugins/fbx/mesh/FbxPolygon.java | 59 ++ .../plugins/fbx/misc/FbxGlobalSettings.java | 145 ++++ .../jme3/scene/plugins/fbx/node/FbxNode.java | 617 ++++++++++++++++++ .../plugins/fbx/node/FbxNodeAttribute.java | 41 ++ .../scene/plugins/fbx/node/FbxNodeUtil.java | 61 ++ .../plugins/fbx/node/FbxNullAttribute.java | 59 ++ .../scene/plugins/fbx/node/FbxRootNode.java | 45 ++ .../jme3/scene/plugins/fbx/obj/FbxObject.java | 144 ++++ .../plugins/fbx/obj/FbxObjectFactory.java | 209 ++++++ .../plugins/fbx/obj/FbxUnknownObject.java | 54 ++ .../jme3/scene/plugins/IrBoneWeightIndex.java | 89 +++ .../java/com/jme3/scene/plugins/IrMesh.java | 46 ++ .../com/jme3/scene/plugins/IrPolygon.java | 46 ++ .../java/com/jme3/scene/plugins/IrUtils.java | 400 ++++++++++++ .../java/com/jme3/scene/plugins/IrVertex.java | 170 +++++ 34 files changed, 5357 insertions(+) create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java 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/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/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/mesh/FbxPolygon.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java new file mode 100644 index 000000000..bb7773785 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.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.mesh; + +import java.util.Arrays; +import java.util.List; + +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(); + } +} From aeb1b547ced55a9de829faaf194f455d5eef9acc Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 19 Apr 2015 21:03:51 -0400 Subject: [PATCH 081/225] FBX: fix build errors (missing functions that need to be added) --- .../main/java/com/jme3/animation/Bone.java | 20 +++++++++++++++++++ .../src/main/java/com/jme3/math/FastMath.java | 19 ++++++++++++++++++ .../main/java/com/jme3/math/Transform.java | 20 +++++++++++++++++++ 3 files changed, 59 insertions(+) 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. */ From 92b2ec664e7ef50f58b53d6e2b05647fcdbfca58 Mon Sep 17 00:00:00 2001 From: Turakar Date: Mon, 20 Apr 2015 20:01:52 +0200 Subject: [PATCH 082/225] Fixed typo #258 --- .../common/java/com/jme3/bullet/control/RigidBodyControl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From 7e78651ed45b0dd2b471bda7fb8996aa3e69aafb Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 22 Apr 2015 20:55:56 +0200 Subject: [PATCH 083/225] Shader Nodes : fixed an issue in the shader generator that was caussing an error when there was a "//" comment in the declarative section of a node glsl code --- .../src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java | 1 + 1 file changed, 1 insertion(+) 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(); } From 9ba04bed8fa16a7d7fd96ce8afb6d219703e6080 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 24 Apr 2015 23:31:52 -0400 Subject: [PATCH 084/225] GLRenderer: Merge Color and Screen BlendModes as they are they same --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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..32d8e6b24 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 @@ -704,9 +704,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 +716,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; From 73f53931f02e6e9e9a38e6a09affb693523dd72a Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 24 Apr 2015 23:35:30 -0400 Subject: [PATCH 085/225] Image: fix mipmap generation flags not properly being updated --- .../src/main/java/com/jme3/texture/Image.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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..4225da7d3 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); From ca6b492cea4ab814115b705b869462a2e406abb1 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 24 Apr 2015 23:46:56 -0400 Subject: [PATCH 086/225] SSAOFilter: remove excessive GPU mipmap generation (not needed for filters) --- jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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()); From 389b117fb64388724f9e25a18bac3ff8ee008fae Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 12:45:30 -0400 Subject: [PATCH 087/225] IosGL: reset buffer position in fromArray() --- jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java | 2 ++ 1 file changed, 2 insertions(+) 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..a1dfaa757 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 @@ -90,7 +90,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) { From 5b95f8a4b0e70a9ef5839be199f5274a94247f02 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 16:57:24 -0400 Subject: [PATCH 088/225] GLRenderer: Improve compatibility with OpenGL 3.2 core profile * Separate GLFbo and GLExt implementations. GLFbo can now be implemented either via vanilla OpenGL3 calls or GL_EXT_framebuffer_*** extensions (OpenGL2.1- only). * Use modern way of getting supported extensions in core profile. * Luminance and Alpha formats are not available when running in core profile. * Bind a dummy vertex array object (VAO) when running in core profile. * Point sprite mode is always enabled. Since both OpenGL ES 2.0 and OpenGL 3.2 core require it, jME3 is no longer capable of rendering regular points. --- .../com/jme3/system/android/OGLESContext.java | 3 +- .../src/main/java/com/jme3/renderer/Caps.java | 14 +- .../java/com/jme3/renderer/opengl/GL.java | 1 - .../java/com/jme3/renderer/opengl/GL3.java | 10 +- .../jme3/renderer/opengl/GLDebugDesktop.java | 19 ++- .../com/jme3/renderer/opengl/GLDebugES.java | 10 +- .../java/com/jme3/renderer/opengl/GLExt.java | 4 +- .../java/com/jme3/renderer/opengl/GLFbo.java | 3 +- .../jme3/renderer/opengl/GLImageFormats.java | 38 +++-- .../com/jme3/renderer/opengl/GLRenderer.java | 160 ++++++++---------- .../com/jme3/renderer/opengl/GLTracer.java | 7 +- .../com/jme3/system/ios/IGLESContext.java | 5 +- .../java/com/jme3/renderer/lwjgl/LwjglGL.java | 12 +- .../com/jme3/renderer/lwjgl/LwjglGLExt.java | 68 +------- .../jme3/renderer/lwjgl/LwjglGLFboEXT.java | 98 +++++++++++ .../jme3/renderer/lwjgl/LwjglGLFboGL3.java | 96 +++++++++++ .../com/jme3/system/lwjgl/LwjglContext.java | 26 ++- 17 files changed, 383 insertions(+), 191 deletions(-) create mode 100644 jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java create mode 100644 jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java 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-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..21ca7a7fa 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; 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..5fbaaaa52 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,13 +3,13 @@ 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; - 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; } @@ -73,5 +73,18 @@ 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(); + } } 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..3e8850589 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,12 +10,10 @@ 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 glActiveTexture(int texture) { @@ -478,7 +476,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 +523,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 32d8e6b24..7b928202f 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 @@ -112,13 +112,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 +137,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 +194,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 +254,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 +262,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 +290,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 +386,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 +445,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 +511,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 +644,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); @@ -1288,24 +1297,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(); } @@ -1313,12 +1322,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"); } @@ -1364,7 +1373,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; } @@ -1382,13 +1391,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()); @@ -1398,7 +1407,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. @@ -1407,7 +1416,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) { @@ -1425,7 +1434,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(), @@ -1443,9 +1452,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()); } } @@ -1463,7 +1472,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; @@ -1543,7 +1552,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; @@ -1575,7 +1584,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(); @@ -1615,7 +1624,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(); @@ -1627,7 +1636,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(); } } @@ -1656,7 +1665,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(); } } @@ -1680,7 +1689,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; } @@ -2618,32 +2627,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/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-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-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 82b8ee72b..0b55a3a40 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) { @@ -237,6 +237,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 +448,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..57ef81c0a 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,21 +208,30 @@ 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("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()); From a6c71c4f50fe15ff94c335dec5da84d3d08f764c Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 17:34:29 -0400 Subject: [PATCH 089/225] Fix missing glPatchParameter and another syntax error --- .../main/java/com/jme3/renderer/opengl/GLDebugDesktop.java | 7 +++++++ .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 1 + 2 files changed, 8 insertions(+) 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 5fbaaaa52..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 @@ -7,11 +7,13 @@ 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, 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) { @@ -87,4 +89,9 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 { checkError(); } + @Override + public void glPatchParameter(int count) { + gl4.glPatchParameter(count); + checkError(); + } } 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 7b928202f..80abfaf9c 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; From ed4b70bcad5052ff76b509a3f39e1647102115c7 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 17:41:50 -0400 Subject: [PATCH 090/225] GLTiming: new GL wrapper to profile GL calls --- .../com/jme3/renderer/android/AndroidGL.java | 3 + .../java/com/jme3/renderer/opengl/GL.java | 2 + .../com/jme3/renderer/opengl/GLDebugES.java | 4 + .../com/jme3/renderer/opengl/GLRenderer.java | 1 + .../com/jme3/renderer/opengl/GLTiming.java | 122 ++++++++++++++++++ .../jme3/renderer/opengl/GLTimingState.java | 41 ++++++ .../java/com/jme3/renderer/ios/IosGL.java | 3 + .../java/com/jme3/renderer/lwjgl/LwjglGL.java | 3 + .../com/jme3/system/lwjgl/LwjglContext.java | 7 + 9 files changed, 186 insertions(+) create mode 100644 jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java create mode 100644 jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java 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-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index 21ca7a7fa..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 @@ -177,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/GLDebugES.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java index 3e8850589..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 @@ -16,6 +16,10 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt { this.glfbo = glfbo; } + public void resetStats() { + gl.resetStats(); + } + public void glActiveTexture(int texture) { gl.glActiveTexture(texture); checkError(); 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 80abfaf9c..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 @@ -870,6 +870,7 @@ public class GLRenderer implements Renderer { public void postFrame() { objManager.deleteUnused(this); + gl.resetStats(); } /*********************************************************************\ 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-ios/src/main/java/com/jme3/renderer/ios/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java index a1dfaa757..bf82fdff3 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 @@ -50,6 +50,9 @@ public class IosGL implements GL, GLExt { private final int[] temp_array = new int[16]; + public void resetStats() { + } + private static int getLimitBytes(ByteBuffer buffer) { checkLimit(buffer); return buffer.limit(); 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 0b55a3a40..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 @@ -27,6 +27,9 @@ public class LwjglGL implements GL, GL2, GL3, GL4 { } } + public void resetStats() { + } + public void glActiveTexture(int param1) { GL13.glActiveTexture(param1); } 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 57ef81c0a..27e36d78f 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 @@ -225,6 +225,13 @@ public abstract class LwjglContext implements JmeContext { 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, GL4.class); glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); From aba48495e12ace8ef7001d22d794a5a39b529d0c Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 18:25:07 -0400 Subject: [PATCH 091/225] J3MLoader: enforce MaterialKey requirements based on extension --- .../java/com/jme3/material/plugins/J3MLoader.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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{ From 10cde0a4b28ef13fe9504d3f518a48b62e930921 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 22:42:20 -0400 Subject: [PATCH 092/225] DXTFlipper: fix incorrect flipping of DXT5 images of size 2x2 For DXT1/3 images, the format for color and alpha blocks is the same, so the bug would not appear. For DXT5 images, the alpha block is formatted differently. The issue is that it flips the color block and then the alpha block for 2x2 images, but the correct order is alpha block then color block. --- .../com/jme3/texture/plugins/DXTFlipper.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) 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){ From 0a3e9a434e84e8c264b4fd143db011d2df67fcdf Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 23:09:11 -0400 Subject: [PATCH 093/225] Image: treat setMipMapSizes as a request to generate mips --- jme3-core/src/main/java/com/jme3/texture/Image.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4225da7d3..96ab62819 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -791,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(); From 5cf6b0c9a63229b1dee5ab31821b3bd2bd913386 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 23:09:58 -0400 Subject: [PATCH 094/225] LwjglContext: add custom handler for GL debug messages --- .../com/jme3/system/lwjgl/LwjglContext.java | 2 +- .../lwjgl/LwjglGLDebugOutputHandler.java | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java 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 27e36d78f..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 @@ -245,7 +245,7 @@ public abstract class LwjglContext implements JmeContext { } 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)); + } + +} From 0c846eaf6a9283ac0957c58605249c5299c66c29 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 23:11:10 -0400 Subject: [PATCH 095/225] SPLighting: fix syntax errors with vertex lighting + color ramp --- .../resources/Common/MatDefs/Light/SPLighting.vert | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 From 1fec72605f6927f47143146b1047c8d5c60ea94c Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 25 Apr 2015 23:13:20 -0400 Subject: [PATCH 096/225] SPLighting.frag: fix syntax error with vertex lighting enabled --- .../Common/MatDefs/Light/SPLighting.frag | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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; From c9eaeeea12f68e5d88e4142cc0c9ac51f88ef5ff Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sat, 25 Apr 2015 23:47:26 -0400 Subject: [PATCH 097/225] Added some message delegator util classes that makes it easier to handle network messages. These delegators can introspect a delegate type to find message-type specific handler methods. This mapping can be done automatically or performed manually. --- .../util/AbstractMessageDelegator.java | 313 ++++++++++++++++++ .../network/util/ObjectMessageDelegator.java | 72 ++++ .../network/util/SessionDataDelegator.java | 103 ++++++ 3 files changed, 488 insertions(+) create mode 100644 jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java 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..b077f9e01 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java @@ -0,0 +1,313 @@ +/* + * 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 no + * arguments, the connection and message type arguments (in that order), + * or just the message type or connection argument. + */ + 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 connectionIndex = parms.length > 1 ? 0 : -1; + int messageIndex = parms.length > 1 ? 1 : 0; + + if( connectionIndex > 0 && !MessageConnection.class.isAssignableFrom(parms[connectionIndex]) ) { + 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; + } +} + From 9abedf284e5047d5b13ef3df6f230c10d0ab271a Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 00:09:24 -0400 Subject: [PATCH 098/225] Added a message that can be used to compile and send the serializer registry... and then register them on the other end. --- .../SerializerRegistrationsMessage.java | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java 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 + "]"; + } + } +} + + + From 1eb2ba72768b355eb4ff6fa6b128be849dea5ef2 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 00:24:51 -0400 Subject: [PATCH 099/225] Adding an initial implementation for a service manager module with custom subclasses and service interfaces for client services and server-side services (HostedServices). This code is a copy and refactoring of code I developed for Mythruna... it worked there but I haven't tested it yet in its new form. Things may change as I integrate this more closely with the core Client and Server classes. I wanted to get it into source control first. Also included an RPC service implementation which can serve as the underpinning for other things. Coming soon: serializer registration service and a simple RMI service based on the RPC layer. --- .../service/AbstractClientService.java | 51 ++++ .../service/AbstractHostedService.java | 70 +++++ .../jme3/network/service/AbstractService.java | 111 ++++++++ .../jme3/network/service/ClientService.java | 72 +++++ .../network/service/ClientServiceManager.java | 87 ++++++ .../jme3/network/service/HostedService.java | 74 ++++++ .../network/service/HostedServiceManager.java | 127 +++++++++ .../com/jme3/network/service/Service.java | 67 +++++ .../jme3/network/service/ServiceManager.java | 160 ++++++++++++ .../network/service/rpc/RpcClientService.java | 123 +++++++++ .../network/service/rpc/RpcConnection.java | 247 ++++++++++++++++++ .../jme3/network/service/rpc/RpcHandler.java | 52 ++++ .../network/service/rpc/RpcHostedService.java | 227 ++++++++++++++++ .../service/rpc/msg/RpcCallMessage.java | 98 +++++++ .../service/rpc/msg/RpcResponseMessage.java | 89 +++++++ 15 files changed, 1655 insertions(+) create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/ClientService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/HostedService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/Service.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java 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..df8957201 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java @@ -0,0 +1,87 @@ +/* + * 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); + } + + /** + * 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..866357852 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java @@ -0,0 +1,127 @@ +/* + * 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); + } + + /** + * 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..530237cea --- /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 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..b5e66c188 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java @@ -0,0 +1,247 @@ +/* + * 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}); + } + connection.send(channel, 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); + } + + /** + * 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 { + Object result = handler.call(this, msg.getObjectId(), msg.getProcedureId(), msg.getArguments()); + if( !msg.isAsync() ) { + RpcResponseMessage response = new RpcResponseMessage(msg.getMessageId(), result); + connection.send(msg.getChannel(), response); + } + } catch( Exception e ) { + if( !msg.isAsync() ) { + RpcResponseMessage response = new RpcResponseMessage(msg.getMessageId(), e); + connection.send(msg.getChannel(), response); + } 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 + + "]"; + } +} From 35155c6b5bf282de5d102f2643a8a117dd226d46 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 00:57:02 -0400 Subject: [PATCH 100/225] Integrated the new service manager stuff with Client and Server. (Untested at the moment but straight forward.) Also fixed a small but silent bug in DefaultServer when closing out endpoints that were never fully connected. Garbage was left around in the "connecting" data structure. --- .../main/java/com/jme3/network/Client.java | 6 +++ .../main/java/com/jme3/network/Server.java | 8 +++ .../com/jme3/network/base/DefaultClient.java | 23 +++++++- .../com/jme3/network/base/DefaultServer.java | 52 +++++++++++++++++-- .../com/jme3/network/service/Service.java | 2 +- 5 files changed, 83 insertions(+), 8 deletions(-) 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..ee7aba4d4 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Client.java +++ b/jme3-networking/src/main/java/com/jme3/network/Client.java @@ -72,6 +72,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..3d9ceb149 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,7 @@ 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 java.io.IOException; import java.nio.ByteBuffer; import java.util.*; @@ -54,7 +55,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 +81,13 @@ 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); } public DefaultClient( String gameName, int version, Connector reliable, Connector fast, @@ -200,6 +204,11 @@ public class DefaultClient implements Client { return version; } + + public ClientServiceManager getServices() + { + return services; + } public void send( Message message ) { @@ -260,7 +269,7 @@ public class DefaultClient implements Client { checkRunning(); - closeConnections( null ); + closeConnections( null ); } protected void closeConnections( DisconnectInfo info ) @@ -268,6 +277,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 +298,9 @@ public class DefaultClient implements Client fireDisconnected(info); isRunning = false; + + // Terminate the services + services.terminate(); } public void addClientStateListener( ClientStateListener listener ) @@ -329,6 +345,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..a6bd9f763 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,7 @@ 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 java.io.IOException; import java.nio.ByteBuffer; import java.util.*; @@ -55,7 +56,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 +86,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 +95,7 @@ public class DefaultServer implements Server this.gameName = gameName; this.version = version; + this.services = new HostedServiceManager(this); reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true ); channels.add( reliableAdapter ); @@ -110,6 +114,11 @@ public class DefaultServer implements Server { return version; } + + public HostedServiceManager getServices() + { + return services; + } public int addChannel( int port ) { @@ -164,7 +173,10 @@ public class DefaultServer implements Server ka.start(); } - isRunning = true; + isRunning = true; + + // Start the services + services.start(); } public boolean isRunning() @@ -177,13 +189,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 +415,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 +442,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 +483,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 +598,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/service/Service.java b/jme3-networking/src/main/java/com/jme3/network/service/Service.java index 530237cea..f56c90dda 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/Service.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/Service.java @@ -53,7 +53,7 @@ public interface Service { public void start(); /** - * Called when the service is shutting down. All services + * Called when the service manager is shutting down. All services * are stopped and any service manager resources are closed * before the services are terminated. */ From 1145f99d03d281534f11dc298d6df5d8d32ec6a9 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 01:19:38 -0400 Subject: [PATCH 101/225] Added a service that will send the server's serializer registration set to each new client that connects. The client-side version of this service will then register them all. This means that serialized classes need only be registered on the server. I've modified DefaultClient and DefaultServer to register these services by default because they make other services easier to write and because they will save people lots of trouble. I'm 90% sure there are no bad side-effects for people who choose to continue doing things the old way but it may depend on when they register their serializers in relations to creating the client and server objects. --- .../com/jme3/network/base/DefaultClient.java | 6 ++ .../com/jme3/network/base/DefaultServer.java | 8 ++- .../network/service/ClientServiceManager.java | 13 ++++ .../network/service/HostedServiceManager.java | 13 ++++ .../ClientSerializerRegistrationsService.java | 68 ++++++++++++++++++ .../ServerSerializerRegistrationsService.java | 71 +++++++++++++++++++ 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java 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 3d9ceb149..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 @@ -38,6 +38,7 @@ 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.*; @@ -88,6 +89,7 @@ public class DefaultClient implements Client this.gameName = gameName; this.version = version; this.services = new ClientServiceManager(this); + addStandardServices(); } public DefaultClient( String gameName, int version, Connector reliable, Connector fast, @@ -97,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 ) 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 a6bd9f763..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 @@ -38,6 +38,7 @@ 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.*; @@ -95,7 +96,8 @@ public class DefaultServer implements Server this.gameName = gameName; this.version = version; - this.services = new HostedServiceManager(this); + this.services = new HostedServiceManager(this); + addStandardServices(); reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true ); channels.add( reliableAdapter ); @@ -105,6 +107,10 @@ public class DefaultServer implements Server } } + protected void addStandardServices() { + services.addService(new ServerSerializerRegistrationsService()); + } + public String getGameName() { return gameName; 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 index df8957201..dc204fb2f 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java @@ -75,6 +75,19 @@ public class ClientServiceManager extends ServiceManager { 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 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 index 866357852..3606ba44b 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java @@ -82,6 +82,19 @@ public class HostedServiceManager extends ServiceManager { 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 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..6705bbc8b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -0,0 +1,68 @@ +/* + * 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); + + // 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..8a0cd94f1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java @@ -0,0 +1,71 @@ +/* + * 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); + } + + @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); + } +} From 8b34e4890a8f165bde42edb64e15fa418ece0719 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 02:26:47 -0400 Subject: [PATCH 102/225] Fixed a comment to be more accurate with respect to handler method argument types. Fixed a small bug in how auto-detect worked. It was too greedy in looking for two-argument methods and would somehow allow methods that took a first argument that was not a connection type. --- .../util/AbstractMessageDelegator.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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 index b077f9e01..a73496ea8 100644 --- a/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java +++ b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java @@ -91,9 +91,8 @@ public abstract class AbstractMessageDelegator * 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 no - * arguments, the connection and message type arguments (in that order), - * or just the message type or connection argument. + * 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 ) { @@ -106,13 +105,15 @@ public abstract class AbstractMessageDelegator if( parms.length != 2 && parms.length != 1 ) { log.finest("Parameter count is not 1 or 2"); return false; - } - int connectionIndex = parms.length > 1 ? 0 : -1; - int messageIndex = parms.length > 1 ? 1 : 0; - - if( connectionIndex > 0 && !MessageConnection.class.isAssignableFrom(parms[connectionIndex]) ) { - log.finest("First paramter is not a MessageConnection or subclass."); - 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]) ) { From dd65580bf360cc50bc7a5f93e2fee02ca252a3fe Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 02:27:27 -0400 Subject: [PATCH 103/225] Pre-register the inner class as well. A cut paste error from my originals. --- .../service/serializer/ClientSerializerRegistrationsService.java | 1 + .../service/serializer/ServerSerializerRegistrationsService.java | 1 + 2 files changed, 2 insertions(+) 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 index 6705bbc8b..911ce0fb1 100644 --- 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 @@ -55,6 +55,7 @@ public class ClientSerializerRegistrationsService extends AbstractClientService // 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); 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 index 8a0cd94f1..b24a8db5f 100644 --- 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 @@ -51,6 +51,7 @@ public class ServerSerializerRegistrationsService extends AbstractHostedService protected void onInitialize( HostedServiceManager serviceManager ) { // Make sure our message type is registered Serializer.registerClass(SerializerRegistrationsMessage.class); + Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); } @Override From 96dab5f5617ba544d05bf3a6e9c0d983a61afb12 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 02:28:38 -0400 Subject: [PATCH 104/225] Allow remote calls to be made on the default channel instead of just custom channels. Custom channels are 0-max channel while -1 indicates the default send(). --- .../network/service/rpc/RpcConnection.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) 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 index b5e66c188..b78316bf3 100644 --- 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 @@ -116,7 +116,11 @@ public class RpcConnection { if( log.isLoggable(Level.FINEST) ) { log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); } - connection.send(channel, msg); + if( channel >= 0 ) { + connection.send(channel, msg); + } else { + connection.send(msg); + } return holder.getResponse(); } @@ -159,6 +163,14 @@ public class RpcConnection { 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. @@ -170,15 +182,16 @@ public class RpcConnection { } 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() ) { - RpcResponseMessage response = new RpcResponseMessage(msg.getMessageId(), result); - connection.send(msg.getChannel(), response); + send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), result)); } } catch( Exception e ) { if( !msg.isAsync() ) { - RpcResponseMessage response = new RpcResponseMessage(msg.getMessageId(), e); - connection.send(msg.getChannel(), response); + send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), e)); } else { log.log(Level.SEVERE, "Error invoking async call for:" + msg, e); } From 7bea2cc9c7b0731dda7cbe8ff981dce1aad50386 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 26 Apr 2015 03:10:47 -0400 Subject: [PATCH 105/225] Fixed a missing import. --- jme3-networking/src/main/java/com/jme3/network/Client.java | 2 ++ 1 file changed, 2 insertions(+) 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 ee7aba4d4..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 From eba4c4e29a551c4a298584797ed48cd8b3e9cc00 Mon Sep 17 00:00:00 2001 From: Kostyantyn Hushchyn Date: Sun, 26 Apr 2015 11:06:26 +0300 Subject: [PATCH 106/225] Fixed iOS subsystem after transition to common renderer. Added OpenAL audio renderer. Implemented flip y in native iOS image loader --- jme3-ios/src/main/java/com/jme3/asset/IOS.cfg | 4 + .../main/java/com/jme3/audio/android/AL.java | 1054 ----------------- .../jme3/audio/android/AndroidAudioData.java | 67 -- .../main/java/com/jme3/audio/ios/IosAL.java | 53 + .../main/java/com/jme3/audio/ios/IosALC.java | 26 + .../main/java/com/jme3/audio/ios/IosEFX.java | 32 + .../audio/plugins/AndroidAudioLoader.java | 20 - .../java/com/jme3/renderer/ios/IosGL.java | 3 +- .../com/jme3/system/ios/IosImageLoader.java | 15 +- .../com/jme3/system/ios/JmeIosSystem.java | 15 +- .../src/main/resources/com/jme3/asset/IOS.cfg | 8 + 11 files changed, 147 insertions(+), 1150 deletions(-) delete mode 100644 jme3-ios/src/main/java/com/jme3/audio/android/AL.java delete mode 100644 jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java create mode 100644 jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java create mode 100644 jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java create mode 100644 jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java delete mode 100644 jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java create mode 100644 jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg 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..4e4052447 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,7 @@ INCLUDE com/jme3/asset/General.cfg # IOS specific loaders LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg +LOADER com.jme3.material.plugins.J3MLoader : j3m, 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/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java index bf82fdff3..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,7 +47,7 @@ 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]; 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..4e4052447 --- /dev/null +++ b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg @@ -0,0 +1,8 @@ +INCLUDE com/jme3/asset/General.cfg + +# IOS specific loaders +LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg +LOADER com.jme3.material.plugins.J3MLoader : j3m, 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 From fc8cebe17d45bb3af07127c91d5a3cc603b039e9 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Sun, 26 Apr 2015 21:55:18 +0200 Subject: [PATCH 107/225] GDK SceneComposer : - The colorAll option for highlightAxisMarker will now highlight only all quads (without axis) --- .../jme3/gde/scenecomposer/SceneEditTool.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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 461f76c0c..5409a3603 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java @@ -57,7 +57,7 @@ public abstract class SceneEditTool { protected Node axisMarker; protected Material redMat, blueMat, greenMat, yellowMat, cyanMat, magentaMat, orangeMat; protected Geometry quadXY, quadXZ, quadYZ; - + protected enum AxisMarkerPickType { axisOnly, planeOnly, axisAndPlane @@ -371,16 +371,17 @@ public abstract class SceneEditTool { axisMarker.getChild("arrowY").setMaterial(orangeMat); } else if (picked == ARROW_Z) { axisMarker.getChild("arrowZ").setMaterial(orangeMat); - } + } else { - if (picked == QUAD_XY || colorAll) { - axisMarker.getChild("quadXY").setMaterial(orangeMat); - } - if (picked == QUAD_XZ || colorAll) { - axisMarker.getChild("quadXZ").setMaterial(orangeMat); - } - if (picked == QUAD_YZ || colorAll) { - axisMarker.getChild("quadYZ").setMaterial(orangeMat); + if (picked == QUAD_XY || colorAll) { + axisMarker.getChild("quadXY").setMaterial(orangeMat); + } + if (picked == QUAD_XZ || colorAll) { + axisMarker.getChild("quadXZ").setMaterial(orangeMat); + } + if (picked == QUAD_YZ || colorAll) { + axisMarker.getChild("quadYZ").setMaterial(orangeMat); + } } } From f2a92a13b381cda6d36d2a6c842ebd5e2bff94f4 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Sun, 26 Apr 2015 22:14:15 +0200 Subject: [PATCH 108/225] GDK SceneComposer : - add a new file : PickManager that provide severals informations for tools. - modified the MoveTool, RotateTool and ScaleTool according with the pickManager - now local/global choice for transformations is just a step away --- .../gde/scenecomposer/tools/MoveTool.java | 112 ++++++++--- .../gde/scenecomposer/tools/PickManager.java | 182 ++++++++++++++++++ .../gde/scenecomposer/tools/RotateTool.java | 123 ++++++------ .../gde/scenecomposer/tools/ScaleTool.java | 51 +++-- 4 files changed, 363 insertions(+), 105 deletions(-) create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java 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 478a0f7c6..e0e8d009b 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 @@ -5,8 +5,11 @@ package com.jme3.gde.scenecomposer.tools; import com.jme3.asset.AssetManager; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.SceneEditTool; import com.jme3.math.Vector2f; @@ -17,12 +20,11 @@ import org.openide.loaders.DataObject; import org.openide.util.Lookup; /** - * Move an object. - * When created, it generates a quad that will lie along a plane - * that the user selects for moving on. When the mouse is over - * the axisMarker, it will highlight the plane that it is over: XY,XZ,YZ. - * When clicked and then dragged, the selected object will move along that - * plane. + * Move an object. When created, it generates a quad that will lie along a plane + * that the user selects for moving on. When the mouse is over the axisMarker, + * it will highlight the plane that it is over: XY,XZ,YZ. When clicked and then + * dragged, the selected object will move along that plane. + * * @author Brent Owens */ public class MoveTool extends SceneEditTool { @@ -30,7 +32,9 @@ public class MoveTool extends SceneEditTool { private Vector3f pickedMarker; private Vector3f constraintAxis; //used for one axis move private boolean wasDragging = false; - private MoveManager moveManager; + private Vector3f startPosition; + private Vector3f lastPosition; + private PickManager pickManager; public MoveTool() { axisPickType = AxisMarkerPickType.axisAndPlane; @@ -41,7 +45,7 @@ public class MoveTool extends SceneEditTool { @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); - moveManager = Lookup.getDefault().lookup(MoveManager.class); + pickManager = Lookup.getDefault().lookup(PickManager.class); displayPlanes(); } @@ -52,10 +56,10 @@ public class MoveTool extends SceneEditTool { pickedMarker = null; // mouse released, reset selection constraintAxis = Vector3f.UNIT_XYZ; // no constraint if (wasDragging) { - actionPerformed(moveManager.makeUndo()); + actionPerformed(new MoveUndo(toolController.getSelectedSpatial(), startPosition, lastPosition)); wasDragging = false; - } - moveManager.reset(); + } + pickManager.reset(); } } @@ -70,21 +74,21 @@ public class MoveTool extends SceneEditTool { highlightAxisMarker(camera, screenCoord, axisPickType); } else { pickedMarker = null; - moveManager.reset(); + pickManager.reset(); } } @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - if (!pressed) { + if (!pressed) { setDefaultAxisMarkerColors(); pickedMarker = null; // mouse released, reset selection constraintAxis = Vector3f.UNIT_XYZ; // no constraint if (wasDragging) { - actionPerformed(moveManager.makeUndo()); + actionPerformed(new MoveUndo(toolController.getSelectedSpatial(), startPosition, lastPosition)); wasDragging = false; } - moveManager.reset(); + pickManager.reset(); return; } @@ -99,25 +103,43 @@ public class MoveTool extends SceneEditTool { } if (pickedMarker.equals(QUAD_XY)) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XY, true); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, + PickManager.TransformationType.local, camera, screenCoord); } else if (pickedMarker.equals(QUAD_XZ)) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XZ, true); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, + PickManager.TransformationType.local, camera, screenCoord); } else if (pickedMarker.equals(QUAD_YZ)) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.YZ, true); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, + PickManager.TransformationType.local, camera, screenCoord); } else if (pickedMarker.equals(ARROW_X)) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XY, true); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, + PickManager.TransformationType.local, camera, screenCoord); constraintAxis = Vector3f.UNIT_X; // move only X } else if (pickedMarker.equals(ARROW_Y)) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.YZ, true); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, + PickManager.TransformationType.local, camera, screenCoord); constraintAxis = Vector3f.UNIT_Y; // move only Y } else if (pickedMarker.equals(ARROW_Z)) { - moveManager.initiateMove(toolController.getSelectedSpatial(), MoveManager.XZ, true); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, + PickManager.TransformationType.local, camera, screenCoord); constraintAxis = Vector3f.UNIT_Z; // move only Z } + startPosition = toolController.getSelectedSpatial().getLocalTranslation().clone(); + } - if (!moveManager.move(camera, screenCoord, constraintAxis, false)) { + if (!pickManager.updatePick(camera, screenCoord)) { return; } + Vector3f diff = Vector3f.ZERO; + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + diff = pickManager.getTranslation(); + + } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { + diff = pickManager.getTranslation(constraintAxis); + } + Vector3f position = startPosition.add(diff); + lastPosition = position; + toolController.getSelectedSpatial().setLocalTranslation(position); updateToolsTransformation(); wasDragging = true; @@ -126,4 +148,50 @@ public class MoveTool extends SceneEditTool { @Override public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { } + + protected class MoveUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + private Vector3f before = new Vector3f(), after = new Vector3f(); + + MoveUndo(Spatial spatial, Vector3f before, Vector3f after) { + this.spatial = spatial; + this.before.set(before); + if (after != null) { + this.after.set(after); + } + } + + @Override + public void sceneUndo() { + spatial.setLocalTranslation(before); + RigidBodyControl control = spatial.getControl(RigidBodyControl.class); + if (control != null) { + control.setPhysicsLocation(spatial.getWorldTranslation()); + } + CharacterControl character = spatial.getControl(CharacterControl.class); + if (character != null) { + character.setPhysicsLocation(spatial.getWorldTranslation()); + } + // toolController.selectedSpatialTransformed(); + } + + @Override + public void sceneRedo() { + spatial.setLocalTranslation(after); + RigidBodyControl control = spatial.getControl(RigidBodyControl.class); + if (control != null) { + control.setPhysicsLocation(spatial.getWorldTranslation()); + } + CharacterControl character = spatial.getControl(CharacterControl.class); + if (character != null) { + character.setPhysicsLocation(spatial.getWorldTranslation()); + } + //toolController.selectedSpatialTransformed(); + } + + public void setAfter(Vector3f after) { + this.after.set(after); + } + } } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java new file mode 100644 index 000000000..d7075b9fe --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java @@ -0,0 +1,182 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools; + +import com.jme3.gde.scenecomposer.SceneEditTool; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author dokthar + */ +@ServiceProvider(service = PickManager.class) +public class PickManager { + + private Vector3f startPickLoc; + private Vector3f finalPickLoc; + private Vector3f startSpatialLocation; + private Quaternion origineRotation; + private final Node plane; + private Spatial spatial; + + protected static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); + protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 + protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 + + public enum TransformationType { + + local, global, camera + } + + public PickManager() { + float size = 1000; + Geometry g = new Geometry("plane", new Quad(size, size)); + g.setLocalTranslation(-size / 2, -size / 2, 0); + plane = new Node(); + plane.attachChild(g); + } + + public void reset() { + startPickLoc = null; + finalPickLoc = null; + startSpatialLocation = null; + spatial = null; + } + + public void initiatePick(Spatial selectedSpatial, Quaternion planeRotation, TransformationType type, Camera camera, Vector2f screenCoord) { + spatial = selectedSpatial; + startSpatialLocation = selectedSpatial.getWorldTranslation().clone(); + + setTransformation(planeRotation, type); + plane.setLocalTranslation(startSpatialLocation); + + startPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); + } + + public void setTransformation(Quaternion planeRotation, TransformationType type) { + Quaternion rot = new Quaternion(); + if (type == TransformationType.local) { + rot.set(spatial.getWorldRotation()); + rot.multLocal(planeRotation); + origineRotation = spatial.getWorldRotation().clone(); + } else if (type == TransformationType.global) { + rot.set(planeRotation); + origineRotation = new Quaternion(Quaternion.IDENTITY); + } else if (type == TransformationType.camera) { + rot.set(planeRotation); + origineRotation = planeRotation.clone(); + } + plane.setLocalRotation(rot); + } + + public boolean updatePick(Camera camera, Vector2f screenCoord) { + finalPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); + return finalPickLoc != null; + } + + /** + * + * @return the start location in WorldSpace + */ + public Vector3f getStartLocation() { + return startSpatialLocation; + } + + /** + * + * @return the vector from the tool origin to the start location, in + * WorldSpace + */ + public Vector3f getStartOffset() { + return startPickLoc.subtract(startSpatialLocation); + } + + /** + * + * @return the vector from the tool origin to the final location, in + * WorldSpace + */ + public Vector3f getFinalOffset() { + return finalPickLoc.subtract(startSpatialLocation); + } + + /** + * + * @return the angle between the start location and the final location + */ + public float getAngle() { + Vector3f v1, v2; + v1 = startPickLoc.subtract(startSpatialLocation); + v2 = finalPickLoc.subtract(startSpatialLocation); + return v1.angleBetween(v2); + } + + /** + * + * @return the Quaternion rotation in the WorldSpace + */ + public Quaternion getRotation() { + Vector3f v1, v2; + v1 = startPickLoc.subtract(startSpatialLocation).normalize(); + v2 = finalPickLoc.subtract(startSpatialLocation).normalize(); + Vector3f axis = v1.cross(v2); + float angle = v1.angleBetween(v2); + return new Quaternion().fromAngleAxis(angle, axis); + } + + /** + * + * @return the Quaternion rotation in the ToolSpace + */ + public Quaternion getLocalRotation() { + Vector3f v1, v2; + Quaternion rot = origineRotation.inverse(); + v1 = rot.mult(startPickLoc.subtract(startSpatialLocation).normalize()); + v2 = rot.mult(finalPickLoc.subtract(startSpatialLocation).normalize()); + Vector3f axis = v1.cross(v2); + float angle = v1.angleBetween(v2); + return new Quaternion().fromAngleAxis(angle, axis); + } + + /** + * + * @return the translation in WorldSpace + */ + public Vector3f getTranslation() { + return finalPickLoc.subtract(startPickLoc); + } + + /** + * + * @param axisConstrainte + * @return + */ + public Vector3f getTranslation(Vector3f axisConstrainte) { + Vector3f localConstrainte = (origineRotation.mult(axisConstrainte)).normalize(); // according to the "plane" rotation + Vector3f constrainedTranslation = localConstrainte.mult(getTranslation().dot(localConstrainte)); + return constrainedTranslation; + } + + /** + * + * @param axisConstrainte + * @return + */ + public Vector3f getLocalTranslation(Vector3f axisConstrainte) { + //return plane.getWorldRotation().inverse().mult(getTranslation(axisConstrainte)); + return getTranslation(origineRotation.inverse().mult(axisConstrainte)); + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java index 3c4e27b95..343d24818 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java @@ -16,35 +16,38 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import org.openide.loaders.DataObject; - +import org.openide.util.Lookup; + /** * * @author kbender */ public class RotateTool extends SceneEditTool { - - private Vector3f pickedPlane; + + private Vector3f pickedMarker; private Vector2f lastScreenCoord; private Quaternion startRotate; private Quaternion lastRotate; private boolean wasDragging = false; - + private PickManager pickManager; + public RotateTool() { - axisPickType = AxisMarkerPickType.axisAndPlane; + axisPickType = AxisMarkerPickType.planeOnly; setOverrideCameraControl(true); } - + @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); + pickManager = Lookup.getDefault().lookup(PickManager.class); 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; if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); @@ -52,108 +55,94 @@ public class RotateTool extends SceneEditTool { } } } - + @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); - } - else { - pickedPlane = null; + } else { + pickedMarker = 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; - + if (wasDragging) { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); 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(QUAD_XY)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, + PickManager.TransformationType.local, camera, screenCoord); + } else if (pickedMarker.equals(QUAD_XZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, + PickManager.TransformationType.local, camera, screenCoord); + } else if (pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, + PickManager.TransformationType.local, camera, screenCoord); + } startRotate = toolController.getSelectedSpatial().getLocalRotation().clone(); } - - if (lastScreenCoord == null) { - lastScreenCoord = screenCoord; - } else { - Quaternion rotate = new Quaternion(); - float diff; - if(pickedPlane.equals(QUAD_XY)) - { - diff = -(screenCoord.x-lastScreenCoord.x); - diff *= 0.03f; - rotate = rotate.fromAngleAxis(diff, Vector3f.UNIT_Z); - } - else if(pickedPlane.equals(QUAD_YZ)) - { - diff = -(screenCoord.y-lastScreenCoord.y); - diff *= 0.03f; - rotate = rotate.fromAngleAxis(diff, Vector3f.UNIT_X); - } - else if(pickedPlane.equals(QUAD_XZ)) - { - diff = screenCoord.x-lastScreenCoord.x; - diff *= 0.03f; - rotate = rotate.fromAngleAxis(diff, Vector3f.UNIT_Y); - } - - lastScreenCoord = screenCoord; - Quaternion rotation = toolController.getSelectedSpatial().getLocalRotation().mult(rotate); - lastRotate = rotation; + if (!pickManager.updatePick(camera, screenCoord)) { + return; + } + + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + Quaternion rotation = startRotate.mult(pickManager.getLocalRotation()); toolController.getSelectedSpatial().setLocalRotation(rotation); - updateToolsTransformation(); + lastRotate = rotation; } - + updateToolsTransformation(); wasDragging = true; } - + @Override - public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - + public + void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } - + private class ScaleUndo extends AbstractUndoableSceneEdit { - + private Spatial spatial; - private Quaternion before,after; - + private Quaternion before, after; + ScaleUndo(Spatial spatial, Quaternion before, Quaternion after) { this.spatial = spatial; this.before = before; this.after = after; } - + @Override public void sceneUndo() { spatial.setLocalRotation(before); toolController.selectedSpatialTransformed(); } - + @Override public void sceneRedo() { spatial.setLocalRotation(after); 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 cfac57ae1..1f0b5387b 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 @@ -10,11 +10,13 @@ import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.SceneEditTool; +import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import org.openide.loaders.DataObject; +import org.openide.util.Lookup; /** * @@ -24,10 +26,10 @@ public class ScaleTool extends SceneEditTool { private Vector3f pickedMarker; private Vector3f constraintAxis; //used for one axis scale - private Vector2f lastScreenCoord; private Vector3f startScale; private Vector3f lastScale; private boolean wasDragging = false; + private PickManager pickManager; public ScaleTool() { axisPickType = AxisMarkerPickType.axisAndPlane; @@ -37,6 +39,7 @@ public class ScaleTool extends SceneEditTool { @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); + pickManager = Lookup.getDefault().lookup(PickManager.class); displayPlanes(); } @@ -45,12 +48,12 @@ public class ScaleTool extends SceneEditTool { if (!pressed) { setDefaultAxisMarkerColors(); 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; } + pickManager.reset(); } } @@ -63,11 +66,10 @@ public class ScaleTool extends SceneEditTool { public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { if (pickedMarker == null) { highlightAxisMarker(camera, screenCoord, axisPickType, true); + } else { + pickedMarker = null; + pickManager.reset(); } - /*else { - pickedPlane = null; - lastScreenCoord = null; - }*/ } @Override @@ -75,12 +77,12 @@ public class ScaleTool extends SceneEditTool { if (!pressed) { setDefaultAxisMarkerColors(); 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; } + pickManager.reset(); return; } @@ -92,27 +94,44 @@ public class ScaleTool extends SceneEditTool { if (pickedMarker == null) { return; } - if (pickedMarker.equals(ARROW_X)) { + + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), + PickManager.TransformationType.camera, camera, screenCoord); + } else if (pickedMarker.equals(ARROW_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, + PickManager.TransformationType.global, camera, screenCoord); constraintAxis = Vector3f.UNIT_X; // scale only X } else if (pickedMarker.equals(ARROW_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, + PickManager.TransformationType.global, camera, screenCoord); constraintAxis = Vector3f.UNIT_Y; // scale only Y } else if (pickedMarker.equals(ARROW_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, + PickManager.TransformationType.global, camera, screenCoord); constraintAxis = Vector3f.UNIT_Z; // scale only Z } startScale = toolController.getSelectedSpatial().getLocalScale().clone(); } - if (lastScreenCoord == null) { - lastScreenCoord = screenCoord; - } else { - float diff = screenCoord.y - lastScreenCoord.y; - diff *= 0.1f; - lastScreenCoord = screenCoord; - Vector3f scale = toolController.getSelectedSpatial().getLocalScale().add(new Vector3f(diff, diff, diff).multLocal(constraintAxis)); + if (!pickManager.updatePick(camera, screenCoord)) { + return; + } + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + constraintAxis = pickManager.getStartOffset().normalize(); + float diff = pickManager.getTranslation(constraintAxis).dot(constraintAxis); + diff *= 0.5f; + Vector3f scale = startScale.add(new Vector3f(diff, diff, diff)); + lastScale = scale; + toolController.getSelectedSpatial().setLocalScale(scale); + } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { + Vector3f diff = pickManager.getLocalTranslation(constraintAxis); + diff.multLocal(0.5f); + Vector3f scale = startScale.add(diff); lastScale = scale; toolController.getSelectedSpatial().setLocalScale(scale); - updateToolsTransformation(); } + updateToolsTransformation(); wasDragging = true; } From b35c5e9820b047ca1dcaaac94de40edb73fad445 Mon Sep 17 00:00:00 2001 From: Kostyantyn Hushchyn Date: Mon, 27 Apr 2015 01:36:15 +0300 Subject: [PATCH 109/225] Remove redundand code. Fixed java/lang/IllegalStateException in com/jme3/asset/DesktopAssetManager.registerAndCloneSmartAsset. Added ogg loader --- jme3-ios/src/main/java/com/jme3/asset/IOS.cfg | 4 +- .../renderer/ios/IGLESShaderRenderer.java | 2573 ----------------- .../com/jme3/renderer/ios/TextureUtil.java | 576 ---- .../src/main/resources/com/jme3/asset/IOS.cfg | 4 +- 4 files changed, 6 insertions(+), 3151 deletions(-) delete mode 100644 jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java delete mode 100644 jme3-ios/src/main/java/com/jme3/renderer/ios/TextureUtil.java 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 4e4052447..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,7 +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.material.plugins.J3MLoader : j3m, j3md +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/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/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/resources/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg index 4e4052447..e9d79a459 100644 --- a/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg +++ b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg @@ -2,7 +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.material.plugins.J3MLoader : j3m, j3md +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 From 70b03ea28a568463dbc942fd123aaba78de6c04e Mon Sep 17 00:00:00 2001 From: iwgeric Date: Sun, 26 Apr 2015 19:20:06 -0400 Subject: [PATCH 110/225] Bullet (and jBullet): Remove deprecated PhysicsSpace.enableDebug method in favor of BulletAppState.setDebugEnabled. --- .../java/com/jme3/bullet/BulletAppState.java | 12 +----- .../java/com/jme3/bullet/PhysicsSpace.java | 36 +++--------------- .../java/com/jme3/bullet/PhysicsSpace.java | 38 ++++--------------- 3 files changed, 14 insertions(+), 72 deletions(-) diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java index 05aa9a926..ae278c871 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java @@ -166,7 +166,7 @@ public class BulletAppState implements AppState, PhysicsTickListener { pSpace.addTickListener(this); initialized = true; } - + public void stopPhysics() { if(!initialized){ return; @@ -226,19 +226,9 @@ public class BulletAppState implements AppState, PhysicsTickListener { if (debugEnabled && debugAppState == null && pSpace != null) { debugAppState = new BulletDebugAppState(pSpace); stateManager.attach(debugAppState); - pSpace.enableDebug(app.getAssetManager()); } else if (!debugEnabled && debugAppState != null) { stateManager.detach(debugAppState); debugAppState = null; - if (pSpace != null) { - pSpace.enableDebug(null); - } - } - //TODO: remove when deprecation of PhysicsSpace.enableDebug is through - if (pSpace.getDebugManager() != null && !debugEnabled) { - debugEnabled = true; - } else if (pSpace.getDebugManager() == null && debugEnabled) { - debugEnabled = false; } if (!active) { return; diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 380dc3e16..4485edcd7 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -102,7 +102,6 @@ public class PhysicsSpace { private Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f); private float accuracy = 1f / 60f; private int maxSubSteps = 4, rayTestFlags = 1 << 2; - private AssetManager debugManager; static { // System.loadLibrary("bulletjme"); @@ -702,7 +701,7 @@ public class PhysicsSpace { public Vector3f getGravity(Vector3f gravity) { return gravity.set(this.gravity); } - + // /** // * applies gravity value to all objects // */ @@ -783,7 +782,7 @@ public class PhysicsSpace { public void SetRayTestFlags(int flags) { rayTestFlags = flags; } - + /** * Gets m_flags for raytest, see https://code.google.com/p/bullet/source/browse/trunk/src/BulletCollision/NarrowPhaseCollision/btRaycastCallback.h * for possible options. @@ -792,7 +791,7 @@ public class PhysicsSpace { public int GetRayTestFlags() { return rayTestFlags; } - + /** * Performs a ray collision test and returns the results as a list of * PhysicsRayTestResults @@ -837,7 +836,7 @@ public class PhysicsSpace { return (List) results; } - public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { + public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { return sweepTest(shape, start, end, results, 0.0f); } @@ -849,7 +848,7 @@ public class PhysicsSpace { * collision if it starts INSIDE an object and is moving AWAY from its * center. */ - public List sweepTest(CollisionShape shape, Transform start, Transform end, List results, float allowedCcdPenetration ) { + public List sweepTest(CollisionShape shape, Transform start, Transform end, List results, float allowedCcdPenetration ) { results.clear(); sweepTest_native(shape.getObjectId(), start, end, physicsSpaceId, results, allowedCcdPenetration); return results; @@ -872,7 +871,7 @@ public class PhysicsSpace { } */ - + /** * destroys the current PhysicsSpace so that a new one can be created */ @@ -959,29 +958,6 @@ public class PhysicsSpace { this.worldMax.set(worldMax); } - /** - * Enable debug display for physics. - * - * @deprecated in favor of BulletDebugAppState, use - * BulletAppState.setDebugEnabled(boolean) to add automatically - * @param manager AssetManager to use to create debug materials - */ - @Deprecated - public void enableDebug(AssetManager manager) { - debugManager = manager; - } - - /** - * Disable debug display - */ - public void disableDebug() { - debugManager = null; - } - - public AssetManager getDebugManager() { - return debugManager; - } - public static native void initNativePhysics(); /** diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index dc61e6c6c..3c8a3b225 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -142,7 +142,6 @@ public class PhysicsSpace { private javax.vecmath.Vector3f rayVec2 = new javax.vecmath.Vector3f(); private com.bulletphysics.linearmath.Transform sweepTrans1 = new com.bulletphysics.linearmath.Transform(new javax.vecmath.Matrix3f()); private com.bulletphysics.linearmath.Transform sweepTrans2 = new com.bulletphysics.linearmath.Transform(new javax.vecmath.Matrix3f()); - private AssetManager debugManager; /** * Get the current PhysicsSpace running on this thread
@@ -362,7 +361,7 @@ public class PhysicsSpace { eventFactory.recycle(physicsCollisionEvent); } } - + public static Future enqueueOnThisThread(Callable callable) { AppTask task = new AppTask(callable); System.out.println("created apptask"); @@ -620,7 +619,7 @@ public class PhysicsSpace { physicsJoints.remove(joint.getObjectId()); dynamicsWorld.removeConstraint(joint.getObjectId()); } - + public Collection getRigidBodyList(){ return new LinkedList(physicsBodies.values()); } @@ -628,19 +627,19 @@ public class PhysicsSpace { public Collection getGhostObjectList(){ return new LinkedList(physicsGhostObjects.values()); } - + public Collection getCharacterList(){ return new LinkedList(physicsCharacters.values()); } - + public Collection getJointList(){ return new LinkedList(physicsJoints.values()); } - + public Collection getVehicleList(){ return new LinkedList(physicsVehicles.values()); } - + /** * Sets the gravity of the PhysicsSpace, set before adding physics objects! * @param gravity @@ -658,7 +657,7 @@ public class PhysicsSpace { dynamicsWorld.getGravity(tempVec); return Converter.convert(tempVec, gravity); } - + /** * applies gravity value to all objects */ @@ -877,29 +876,6 @@ public class PhysicsSpace { this.worldMax.set(worldMax); } - /** - * Enable debug display for physics. - * - * @deprecated in favor of BulletDebugAppState, use - * BulletAppState.setDebugEnabled(boolean) to add automatically - * @param manager AssetManager to use to create debug materials - */ - @Deprecated - public void enableDebug(AssetManager manager) { - debugManager = manager; - } - - /** - * Disable debug display - */ - public void disableDebug() { - debugManager = null; - } - - public AssetManager getDebugManager() { - return debugManager; - } - /** * interface with Broadphase types */ From a52bc0a82c106a4f359daa9f6d6b3aba231551a5 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Sun, 26 Apr 2015 19:21:00 -0400 Subject: [PATCH 111/225] Bullet (and jBullet): Update Test Classes to use BulletAppState.setDebugEnabled instead of PhysicsSpace.enableDebug. --- .../jme3test/bullet/TestAttachDriver.java | 4 +- .../bullet/TestAttachGhostObject.java | 2 +- .../main/java/jme3test/bullet/TestCcd.java | 304 +++++++++--------- .../jme3test/bullet/TestCollisionGroups.java | 4 +- .../bullet/TestCollisionListener.java | 196 +++++------ .../bullet/TestCollisionShapeFactory.java | 2 +- .../java/jme3test/bullet/TestGhostObject.java | 2 +- .../TestKinematicAddToPhysicsSpaceIssue.java | 8 +- .../jme3test/bullet/TestLocalPhysics.java | 2 +- .../java/jme3test/bullet/TestPhysicsCar.java | 2 +- .../bullet/TestPhysicsHingeJoint.java | 4 +- .../jme3test/bullet/TestPhysicsRayCast.java | 18 +- .../jme3test/bullet/TestPhysicsReadWrite.java | 2 +- .../java/jme3test/bullet/TestRagDoll.java | 2 +- .../jme3test/bullet/TestSimplePhysics.java | 2 +- .../java/jme3test/bullet/TestSweepTest.java | 6 +- 16 files changed, 280 insertions(+), 280 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java index a4b4b92a3..8f9447ea1 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java @@ -69,7 +69,7 @@ public class TestAttachDriver extends SimpleApplication implements ActionListene private float accelerationValue = 0; private Vector3f jumpForce = new Vector3f(0, 3000, 0); private BulletAppState bulletAppState; - + public static void main(String[] args) { TestAttachDriver app = new TestAttachDriver(); app.start(); @@ -79,7 +79,7 @@ public class TestAttachDriver extends SimpleApplication implements ActionListene public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); setupKeys(); setupFloor(); buildPlayer(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java index 5b6992f3a..5fac3fc5f 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java @@ -85,7 +85,7 @@ public class TestAttachGhostObject extends SimpleApplication implements AnalogLi public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); setupKeys(); setupJoint(); } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java index beb57bdec..cda07bc85 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java @@ -1,152 +1,152 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Sphere.TextureMode; - -/** - * - * @author normenhansen - */ -public class TestCcd extends SimpleApplication implements ActionListener { - - private Material mat; - private Material mat2; - private Sphere bullet; - private SphereCollisionShape bulletCollisionShape; - private BulletAppState bulletAppState; - - public static void main(String[] args) { - TestCcd app = new TestCcd(); - app.start(); - } - - private void setupKeys() { - inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addMapping("shoot2", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); - inputManager.addListener(this, "shoot"); - inputManager.addListener(this, "shoot2"); - } - - @Override - public void simpleInitApp() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); - bullet = new Sphere(32, 32, 0.4f, true, false); - bullet.setTextureMode(TextureMode.Projected); - bulletCollisionShape = new SphereCollisionShape(0.1f); - setupKeys(); - - mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - mat.getAdditionalRenderState().setWireframe(true); - mat.setColor("Color", ColorRGBA.Green); - - mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - mat2.getAdditionalRenderState().setWireframe(true); - mat2.setColor("Color", ColorRGBA.Red); - - // An obstacle mesh, does not move (mass=0) - Node node2 = new Node(); - node2.setName("mesh"); - node2.setLocalTranslation(new Vector3f(2.5f, 0, 0f)); - node2.addControl(new RigidBodyControl(new MeshCollisionShape(new Box(Vector3f.ZERO, 4, 4, 0.1f)), 0)); - rootNode.attachChild(node2); - getPhysicsSpace().add(node2); - - // The floor, does not move (mass=0) - Node node3 = new Node(); - node3.setLocalTranslation(new Vector3f(0f, -6, 0f)); - node3.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(100, 1, 100)), 0)); - rootNode.attachChild(node3); - getPhysicsSpace().add(node3); - - } - - private PhysicsSpace getPhysicsSpace() { - return bulletAppState.getPhysicsSpace(); - } - - @Override - public void simpleUpdate(float tpf) { - //TODO: add update code - } - - @Override - public void simpleRender(RenderManager rm) { - //TODO: add render code - } - - public void onAction(String binding, boolean value, float tpf) { - if (binding.equals("shoot") && !value) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat); - bulletg.setName("bullet"); - bulletg.setLocalTranslation(cam.getLocation()); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); - bulletg.getControl(RigidBodyControl.class).setCcdMotionThreshold(0.1f); - bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); - rootNode.attachChild(bulletg); - getPhysicsSpace().add(bulletg); - } else if (binding.equals("shoot2") && !value) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat2); - bulletg.setName("bullet"); - bulletg.setLocalTranslation(cam.getLocation()); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); - bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); - rootNode.attachChild(bulletg); - getPhysicsSpace().add(bulletg); - } - } -} +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; + +/** + * + * @author normenhansen + */ +public class TestCcd extends SimpleApplication implements ActionListener { + + private Material mat; + private Material mat2; + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestCcd app = new TestCcd(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping("shoot2", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + inputManager.addListener(this, "shoot"); + inputManager.addListener(this, "shoot2"); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.1f); + setupKeys(); + + mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Green); + + mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.getAdditionalRenderState().setWireframe(true); + mat2.setColor("Color", ColorRGBA.Red); + + // An obstacle mesh, does not move (mass=0) + Node node2 = new Node(); + node2.setName("mesh"); + node2.setLocalTranslation(new Vector3f(2.5f, 0, 0f)); + node2.addControl(new RigidBodyControl(new MeshCollisionShape(new Box(Vector3f.ZERO, 4, 4, 0.1f)), 0)); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // The floor, does not move (mass=0) + Node node3 = new Node(); + node3.setLocalTranslation(new Vector3f(0f, -6, 0f)); + node3.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(100, 1, 100)), 0)); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("shoot") && !value) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat); + bulletg.setName("bullet"); + bulletg.setLocalTranslation(cam.getLocation()); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); + bulletg.getControl(RigidBodyControl.class).setCcdMotionThreshold(0.1f); + bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletg); + } else if (binding.equals("shoot2") && !value) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat2); + bulletg.setName("bullet"); + bulletg.setLocalTranslation(cam.getLocation()); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); + bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletg); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java index 0bfa9f784..410c125d2 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java @@ -61,8 +61,8 @@ public class TestCollisionGroups extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); - + bulletAppState.setDebugEnabled(true); + // Add a physics sphere to the world Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java index 1a13b09fb..3c9450f3d 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java @@ -1,98 +1,98 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionEvent; -import com.jme3.bullet.collision.PhysicsCollisionListener; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.renderer.RenderManager; -import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Sphere.TextureMode; - -/** - * - * @author normenhansen - */ -public class TestCollisionListener extends SimpleApplication implements PhysicsCollisionListener { - - private BulletAppState bulletAppState; - private Sphere bullet; - private SphereCollisionShape bulletCollisionShape; - - public static void main(String[] args) { - TestCollisionListener app = new TestCollisionListener(); - app.start(); - } - - @Override - public void simpleInitApp() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); - bullet = new Sphere(32, 32, 0.4f, true, false); - bullet.setTextureMode(TextureMode.Projected); - bulletCollisionShape = new SphereCollisionShape(0.4f); - - PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); - PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); - - // add ourselves as collision listener - getPhysicsSpace().addCollisionListener(this); - } - - private PhysicsSpace getPhysicsSpace(){ - return bulletAppState.getPhysicsSpace(); - } - - @Override - public void simpleUpdate(float tpf) { - //TODO: add update code - } - - @Override - public void simpleRender(RenderManager rm) { - //TODO: add render code - } - - public void collision(PhysicsCollisionEvent event) { - if ("Box".equals(event.getNodeA().getName()) || "Box".equals(event.getNodeB().getName())) { - if ("bullet".equals(event.getNodeA().getName()) || "bullet".equals(event.getNodeB().getName())) { - fpsText.setText("You hit the box!"); - } - } - } - -} +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; + +/** + * + * @author normenhansen + */ +public class TestCollisionListener extends SimpleApplication implements PhysicsCollisionListener { + + private BulletAppState bulletAppState; + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; + + public static void main(String[] args) { + TestCollisionListener app = new TestCollisionListener(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); + + // add ourselves as collision listener + getPhysicsSpace().addCollisionListener(this); + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } + + public void collision(PhysicsCollisionEvent event) { + if ("Box".equals(event.getNodeA().getName()) || "Box".equals(event.getNodeB().getName())) { + if ("bullet".equals(event.getNodeA().getName()) || "bullet".equals(event.getNodeB().getName())) { + fpsText.setText("You hit the box!"); + } + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java index b2c44ce4e..2128149d2 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java @@ -67,7 +67,7 @@ public class TestCollisionShapeFactory extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); createMaterial(); Node node = new Node("node1"); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java index dde9e530c..74a7c1e1e 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java @@ -62,7 +62,7 @@ public class TestGhostObject extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Mesh to be shared across several boxes. Box boxGeom = new Box(Vector3f.ZERO, 1f, 1f, 1f); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java index 636122b4b..e63cd03e5 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java @@ -60,7 +60,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Add a physics sphere to the world Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); @@ -69,7 +69,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { //Setting the rigidBody to kinematic before adding it to the physic space physicsSphere.getControl(RigidBodyControl.class).setKinematic(true); //adding it to the physic space - getPhysicsSpace().add(physicsSphere); + getPhysicsSpace().add(physicsSphere); //Making it not kinematic again, it should fall under gravity, it doesn't physicsSphere.getControl(RigidBodyControl.class).setKinematic(false); @@ -77,7 +77,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(5, 6, 0)); rootNode.attachChild(physicsSphere2); - + //Adding the rigid body to physic space getPhysicsSpace().add(physicsSphere2); //making it kinematic @@ -85,7 +85,7 @@ public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { //Making it not kinematic again, it works properly, the rigidbody is affected by grvity. physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false); - + // an obstacle mesh, does not move (mass=0) Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java b/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java index ed432da47..3835b1ca0 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java @@ -59,7 +59,7 @@ public class TestLocalPhysics extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Add a physics sphere to the world Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java index 26d7c1a8e..4ba4e060b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java @@ -69,7 +69,7 @@ public class TestPhysicsCar extends SimpleApplication implements ActionListener public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); setupKeys(); buildPlayer(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java index f01ad23f9..029892ad9 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java @@ -76,7 +76,7 @@ public class TestPhysicsHingeJoint extends SimpleApplication implements AnalogLi public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); setupKeys(); setupJoint(); } @@ -102,7 +102,7 @@ public class TestPhysicsHingeJoint extends SimpleApplication implements AnalogLi @Override public void simpleUpdate(float tpf) { - + } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java index c51afd3cc..7a1ea5187 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java @@ -1,5 +1,5 @@ package jme3test.bullet; - + import com.jme3.app.SimpleApplication; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.collision.PhysicsCollisionObject; @@ -16,30 +16,30 @@ import java.util.List; * @author @wezrule */ public class TestPhysicsRayCast extends SimpleApplication { - + private BulletAppState bulletAppState = new BulletAppState(); - + public static void main(String[] args) { new TestPhysicsRayCast().start(); } - + @Override public void simpleInitApp() { stateManager.attach(bulletAppState); initCrossHair(); - + Spatial s = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); s.setLocalScale(0.1f); - + CollisionShape collisionShape = CollisionShapeFactory.createMeshShape(s); Node n = new Node("elephant"); n.addControl(new RigidBodyControl(collisionShape, 1)); n.getControl(RigidBodyControl.class).setKinematic(true); bulletAppState.getPhysicsSpace().add(n); rootNode.attachChild(n); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); } - + @Override public void simpleUpdate(float tpf) { List rayTest = bulletAppState.getPhysicsSpace().rayTest(cam.getLocation(), cam.getLocation().add(cam.getDirection())); @@ -50,7 +50,7 @@ public class TestPhysicsRayCast extends SimpleApplication { fpsText.setText(collisionObject.getUserObject().toString()); } } - + private void initCrossHair() { BitmapText bitmapText = new BitmapText(guiFont); bitmapText.setText("+"); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java index 5724f7a16..7d94e0570 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java @@ -69,7 +69,7 @@ public class TestPhysicsReadWrite extends SimpleApplication{ public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); physicsRootNode=new Node("PhysicsRootNode"); rootNode.attachChild(physicsRootNode); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java index 5768bc021..2752ce5a8 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java @@ -64,7 +64,7 @@ public class TestRagDoll extends SimpleApplication implements ActionListener { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); inputManager.addMapping("Pull ragdoll up", new MouseButtonTrigger(0)); inputManager.addListener(this, "Pull ragdoll up"); PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java b/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java index 2045e5c77..9cf2808ae 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java @@ -59,7 +59,7 @@ public class TestSimplePhysics extends SimpleApplication { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); // Add a physics sphere to the world Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java index 0613e5d22..da9089cec 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java @@ -35,7 +35,7 @@ public class TestSweepTest extends SimpleApplication { public void simpleInitApp() { obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f); capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f); - + stateManager.attach(bulletAppState); capsule = new Node("capsule"); @@ -52,7 +52,7 @@ public class TestSweepTest extends SimpleApplication { bulletAppState.getPhysicsSpace().add(obstacle); rootNode.attachChild(obstacle); - bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.setDebugEnabled(true); } @Override @@ -70,7 +70,7 @@ public class TestSweepTest extends SimpleApplication { colliding = true; } } - + if (!colliding) { // if the sweep is clear then move the spatial capsule.move(move, 0, 0); From ba3e6917c6bf2a729beafec9f71c9157b93e2b50 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 11:00:33 -0400 Subject: [PATCH 112/225] GLRenderer: fix modern extension retrieval method GL_NUM_EXTENSIONS and glGetStringi is only available in OpenGL3.0+. The gl3 != null check will always be true for LWJGL backend. --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 db9b743f0..a6e4f562c 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 @@ -140,7 +140,7 @@ public class GLRenderer implements Renderer { private HashSet loadExtensions() { HashSet extensionSet = new HashSet(64); - if (gl3 != null) { + if (caps.contains(Caps.OpenGL30) { // If OpenGL3+ is available, use the non-deprecated way // of getting supported extensions. gl3.glGetInteger(GL3.GL_NUM_EXTENSIONS, intBuf16); From ab7a45f66c021bad7d9a6d2834b7114cd0b99f2e Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 11:20:01 -0400 Subject: [PATCH 113/225] GLRenderer: forget an end parenthesis --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a6e4f562c..37b7c753f 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 @@ -140,7 +140,7 @@ public class GLRenderer implements Renderer { private HashSet loadExtensions() { HashSet extensionSet = new HashSet(64); - if (caps.contains(Caps.OpenGL30) { + if (caps.contains(Caps.OpenGL30)) { // If OpenGL3+ is available, use the non-deprecated way // of getting supported extensions. gl3.glGetInteger(GL3.GL_NUM_EXTENSIONS, intBuf16); From 03cf96bc04c923ab1a1ce6a9e5846e6d77173486 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 11:56:57 -0400 Subject: [PATCH 114/225] Travis-CI: enable container based infrastructure --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25aedbbe0..93bd5d019 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: java -# jdk: -# - oraclejdk8 +sudo: false branches: only: From f7d544f45398231e13567f2bd86c5ea9de1d3ab2 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 13:00:22 -0400 Subject: [PATCH 115/225] Travis-CI: cache netbeans platform and gradle deps --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 93bd5d019..95bde2086 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: java sudo: false +env: + - GRADLE_USER_HOME=gradle-cache + +cache: + directories: + - gradle-cache + - netbeans branches: only: From cd1d14500571da1d771b9748d6eda912edeb40bd Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 19:03:43 -0400 Subject: [PATCH 116/225] PhysicsSpace: add method to set solver iterations Thanks to Seppes (see thread: http://hub.jmonkeyengine.org/t/how-to-access-native-bullets-constraintsolver-numiterations) --- .../cpp/com_jme3_bullet_PhysicsSpace.cpp | 12 +++++++++ .../native/cpp/com_jme3_bullet_PhysicsSpace.h | 8 ++++++ .../java/com/jme3/bullet/PhysicsSpace.java | 26 ++++++++++++++++++- .../java/com/jme3/bullet/PhysicsSpace.java | 15 +++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp index f9be6a6ad..3ecf7fd6b 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp @@ -528,6 +528,18 @@ extern "C" { space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration); return; } + + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setSolverNumIterations + (JNIEnv *env, jobject object, jlong spaceId, jint value) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + + space->getDynamicsWorld()->getSolverInfo().m_numIterations = value; + } #ifdef __cplusplus } diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h index b499ff04c..0380c17b0 100644 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h @@ -174,6 +174,14 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native (JNIEnv *, jobject, jlong, jobject, jobject, jlong, jobject, jfloat); +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: setSolverNumIterations + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setSolverNumIterations +(JNIEnv *, jobject, jlong, jint); + #ifdef __cplusplus } #endif diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 4485edcd7..edea485ef 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -102,6 +102,7 @@ public class PhysicsSpace { private Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f); private float accuracy = 1f / 60f; private int maxSubSteps = 4, rayTestFlags = 1 << 2; + private int solverNumIterations = 10; static { // System.loadLibrary("bulletjme"); @@ -871,7 +872,7 @@ public class PhysicsSpace { } */ - + /** * destroys the current PhysicsSpace so that a new one can be created */ @@ -958,6 +959,29 @@ public class PhysicsSpace { this.worldMax.set(worldMax); } + /** + * Set the number of iterations used by the contact solver. + * + * The default is 10. Use 4 for low quality, 20 for high quality. + * + * @param numIterations The number of iterations used by the contact & constraint solver. + */ + public void setSolverNumIterations(int numIterations) { + this.solverNumIterations = numIterations; + setSolverNumIterations(physicsSpaceId, numIterations); + } + + /** + * Get the number of iterations used by the contact solver. + * + * @return The number of iterations used by the contact & constraint solver. + */ + public int getSolverNumIterations() { + return solverNumIterations; + } + + private static native void setSolverNumIterations(long physicsSpaceId, int numIterations); + public static native void initNativePhysics(); /** diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 3c8a3b225..016440bf0 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -875,7 +875,22 @@ public class PhysicsSpace { public void setWorldMax(Vector3f worldMax) { this.worldMax.set(worldMax); } + + /** + * Set the number of iterations used by the contact solver. + * + * The default is 10. Use 4 for low quality, 20 for high quality. + * + * @param numIterations The number of iterations used by the contact & constraint solver. + */ + public void setSolverNumIterations(int numIterations) { + dynamicsWorld.getSolverInfo().numIterations = numIterations; + } + public int getSolverNumIterations() { + return dynamicsWorld.getSolverInfo().numIterations; + } + /** * interface with Broadphase types */ From b7af06e41c592e0f2f3f779438c6cb8ede33e3b1 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 19:39:50 -0400 Subject: [PATCH 117/225] SDK Build: ensure netbeans folder is not empty For some reason Travis-CI will create folders that were requested to be cached, thus causing the build to fail. --- sdk/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/build.gradle b/sdk/build.gradle index 745b878c6..b70e80b13 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -48,7 +48,7 @@ task checkPlatformConfig { def platformFile = file("nbproject/private/platform-private.properties") if(!platformFile.exists()){ def netbeansFolder = file("../netbeans") - if(!netbeansFolder.exists()){ + if(!netbeansFolder.exists() || netbeansFolder.list().length == 0){ println "Downloading NetBeans Platform base, this only has to be done once.." def f = file("netbeans.zip") new URL(netbeansUrl).withInputStream{ i -> f.withOutputStream{ it << i }} From bffdb6cc93ac7c1b09815cf42959d794c01b63d1 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 19:45:33 -0400 Subject: [PATCH 118/225] Travis-CI: Add Slack chat notifications --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 95bde2086..2db534d05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,10 @@ branches: only: - master +notifications: + slack: + secure: "Tuoa63n2iJT/JrdmGggnzJRHo7yPigvoEIObddYU2uw4iAkoGhXO125V4z92bggD5idVX/CNr8SCHTgbomzvtq9VrozW75ta2NDMt0bgR9tuxK12QzZpGtU6togU7S0j/0rUrOFi8B3paIc9SfCX6T+hB+Z2hGz8UMpnFuh6xJc=" + before_install: # required libs for android build tools # sudo apt-get update From 9e84c9f572b0cea977cd464579611f02fbf36fac Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 27 Apr 2015 19:57:01 -0400 Subject: [PATCH 119/225] Travis-CI: Fix incorrect notification string --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2db534d05..2e93aa23c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ branches: notifications: slack: - secure: "Tuoa63n2iJT/JrdmGggnzJRHo7yPigvoEIObddYU2uw4iAkoGhXO125V4z92bggD5idVX/CNr8SCHTgbomzvtq9VrozW75ta2NDMt0bgR9tuxK12QzZpGtU6togU7S0j/0rUrOFi8B3paIc9SfCX6T+hB+Z2hGz8UMpnFuh6xJc=" + secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" before_install: # required libs for android build tools From 5c6deeb5aba54d87238363c835a42c2b5792d9aa Mon Sep 17 00:00:00 2001 From: iwgeric Date: Mon, 27 Apr 2015 23:23:26 -0400 Subject: [PATCH 120/225] Android: add GLFbo interface to AndroidGL to fix cast exception --- .../com/jme3/renderer/android/AndroidGL.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 62741253a..b77c4a36d 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 @@ -35,42 +35,43 @@ import android.opengl.GLES20; 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.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; -public class AndroidGL implements GL, GLExt { +public class AndroidGL implements GL, GLExt, GLFbo { public void resetStats() { } - + private static int getLimitBytes(ByteBuffer buffer) { checkLimit(buffer); return buffer.limit(); } - + private static int getLimitBytes(ShortBuffer buffer) { checkLimit(buffer); return buffer.limit() * 2; } - + private static int getLimitBytes(IntBuffer buffer) { checkLimit(buffer); return buffer.limit() * 4; } - + private static int getLimitBytes(FloatBuffer buffer) { checkLimit(buffer); return buffer.limit() * 4; } - + private static int getLimitCount(Buffer buffer, int elementSize) { checkLimit(buffer); return buffer.limit() / elementSize; } - + private static void checkLimit(Buffer buffer) { if (buffer == null) { return; @@ -82,7 +83,7 @@ public class AndroidGL implements GL, GLExt { throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); } } - + public void glActiveTexture(int texture) { GLES20.glActiveTexture(texture); } @@ -130,7 +131,7 @@ public class AndroidGL implements GL, GLExt { public void glBufferSubData(int target, long offset, ByteBuffer data) { GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } - + public void glGetBufferSubData(int target, long offset, ByteBuffer data) { throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); } From 0eac43786282136cc84b74ccb9d7b5e279d313d6 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Mon, 27 Apr 2015 23:56:23 +0200 Subject: [PATCH 121/225] SDK SceneComposer : - added local, global and camera choice into the scenecomposer, this option is displayed into the SceneComposer Window close to scene edit tool. - the scale tool still only work as local - the camera option still need some enhancement --- .../SceneComposerToolController.java | 41 +++++++++++++++++-- .../SceneComposerTopComponent.java | 22 ++++++++++ .../jme3/gde/scenecomposer/SceneEditTool.java | 26 +++++++++++- .../gde/scenecomposer/tools/MoveTool.java | 18 +++----- .../gde/scenecomposer/tools/PickManager.java | 15 +++---- .../gde/scenecomposer/tools/RotateTool.java | 9 ++-- .../gde/scenecomposer/tools/ScaleTool.java | 11 ++--- 7 files changed, 103 insertions(+), 39 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java index dff940e50..2ad04aa8d 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java @@ -10,6 +10,7 @@ import com.jme3.gde.core.scene.SceneApplication; import com.jme3.gde.core.scene.controller.SceneToolController; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.scenecomposer.tools.PickManager; import com.jme3.input.event.KeyInputEvent; import com.jme3.light.Light; import com.jme3.light.PointLight; @@ -50,7 +51,12 @@ public class SceneComposerToolController extends SceneToolController { private boolean snapToScene = false; private boolean selectTerrain = false; private boolean selectGeometries = false; - + private TransformationType transformationType = TransformationType.local; + + public enum TransformationType { + local, global, camera + } + public SceneComposerToolController(final Node toolsNode, AssetManager manager, JmeNode rootNode) { super(toolsNode, manager); this.rootNode = rootNode; @@ -347,8 +353,37 @@ public class SceneComposerToolController extends SceneToolController { public void setSelectGeometries(boolean selectGeometries) { this.selectGeometries = selectGeometries; } - - + + public void setTransformationType(String type) { + if(type != null){ + if(type.equals("Local")){ + setTransformationType(transformationType.local); + } else if(type.equals("Global")){ + setTransformationType(TransformationType.global); + } else if(type.equals("Camera")){ + setTransformationType(TransformationType.camera); + } + } + } + /** + * @return the transformationType + */ + public TransformationType getTransformationType() { + return transformationType; + } + + /** + * @param transformationType the transformationType to set + */ + public void setTransformationType(TransformationType type) { + if(type != this.transformationType){ + this.transformationType = type; + if(editTool != null){ + //update the transform type of the tool + editTool.setTransformType(transformationType); + } + } + } /** * A marker on the screen that shows where a point light or diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java index 1259d74a9..c7ecb9c87 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java @@ -97,6 +97,8 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce sceneInfoLabel1 = new javax.swing.JLabel(); sceneInfoLabel2 = new javax.swing.JLabel(); jToolBar1 = new javax.swing.JToolBar(); + transformationTypeComboBox = new javax.swing.JComboBox(); + jSeparator9 = new javax.swing.JToolBar.Separator(); selectButton = new javax.swing.JToggleButton(); moveButton = new javax.swing.JToggleButton(); rotateButton = new javax.swing.JToggleButton(); @@ -165,6 +167,16 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce jToolBar1.setFloatable(false); jToolBar1.setRollover(true); + transformationTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Local", "Global", "Camera" })); + transformationTypeComboBox.setMaximumSize(new java.awt.Dimension(160, 50)); + transformationTypeComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + transformationTypeComboBoxActionPerformed(evt); + } + }); + jToolBar1.add(transformationTypeComboBox); + jToolBar1.add(jSeparator9); + spatialModButtonGroup.add(selectButton); selectButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/icon_select.png"))); // NOI18N selectButton.setSelected(true); @@ -207,6 +219,7 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce } }); jToolBar1.add(rotateButton); + rotateButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.rotateButton.AccessibleContext.accessibleDescription")); // NOI18N spatialModButtonGroup.add(scaleButton); scaleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/icon_arrow_inout.png"))); // NOI18N @@ -221,6 +234,8 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce } }); jToolBar1.add(scaleButton); + scaleButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.scaleButton.AccessibleContext.accessibleDescription")); // NOI18N + jToolBar1.add(jSeparator5); jToggleScene.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/snapScene.png"))); // NOI18N @@ -644,6 +659,11 @@ private void jToggleSelectTerrainActionPerformed(java.awt.event.ActionEvent evt) private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleSelectGeomActionPerformed toolController.setSelectGeometries(jToggleSelectGeom.isSelected()); }//GEN-LAST:event_jToggleSelectGeomActionPerformed + + private void transformationTypeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_transformationTypeComboBoxActionPerformed + toolController.setTransformationType((String)transformationTypeComboBox.getSelectedItem()); + }//GEN-LAST:event_transformationTypeComboBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton camToCursorSelectionButton; private javax.swing.JButton createPhysicsMeshButton; @@ -671,6 +691,7 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/ private javax.swing.JSeparator jSeparator6; private javax.swing.JToolBar.Separator jSeparator7; private javax.swing.JToolBar.Separator jSeparator8; + private javax.swing.JToolBar.Separator jSeparator9; private javax.swing.JTextField jTextField1; private javax.swing.JToggleButton jToggleGrid; private javax.swing.JToggleButton jToggleScene; @@ -692,6 +713,7 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/ private javax.swing.JToggleButton showGridToggleButton; private javax.swing.JToggleButton showSelectionToggleButton; private javax.swing.ButtonGroup spatialModButtonGroup; + private javax.swing.JComboBox transformationTypeComboBox; // End of variables declaration//GEN-END:variables private void emit(Spatial root) { 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 5409a3603..372984f8e 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java @@ -57,7 +57,8 @@ public abstract class SceneEditTool { protected Node axisMarker; protected Material redMat, blueMat, greenMat, yellowMat, cyanMat, magentaMat, orangeMat; protected Geometry quadXY, quadXZ, quadYZ; - + protected SceneComposerToolController.TransformationType transformType; + protected enum AxisMarkerPickType { axisOnly, planeOnly, axisAndPlane @@ -72,6 +73,7 @@ public abstract class SceneEditTool { public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { this.manager = manager; this.toolController = toolController; + this.setTransformType(toolController.getTransformationType()); //this.selectedSpatial = selectedSpatial; addMarker(toolNode, onTopToolNode); } @@ -130,7 +132,19 @@ public abstract class SceneEditTool { public void doUpdateToolsTransformation() { if (toolController.getSelectedSpatial() != null) { axisMarker.setLocalTranslation(toolController.getSelectedSpatial().getWorldTranslation()); - axisMarker.setLocalRotation(toolController.getSelectedSpatial().getLocalRotation()); + switch (transformType) { + case local: + axisMarker.setLocalRotation(toolController.getSelectedSpatial().getLocalRotation()); + break; + case global: + axisMarker.setLocalRotation(Quaternion.IDENTITY); + break; + case camera: + if(camera != null){ + axisMarker.setLocalRotation(camera.getRotation()); + } + break; + } setAxisMarkerScale(toolController.getSelectedSpatial()); } else { axisMarker.setLocalTranslation(Vector3f.ZERO); @@ -487,4 +501,12 @@ public abstract class SceneEditTool { public void setCamera(Camera camera) { this.camera = camera; } + + public SceneComposerToolController.TransformationType getTransformType() { + return transformType; + } + + public void setTransformType(SceneComposerToolController.TransformationType transformType) { + this.transformType = transformType; + } } 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 e0e8d009b..a553f35f2 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 @@ -103,25 +103,19 @@ public class MoveTool extends SceneEditTool { } if (pickedMarker.equals(QUAD_XY)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); } else if (pickedMarker.equals(QUAD_XZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); } else if (pickedMarker.equals(QUAD_YZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); } else if (pickedMarker.equals(ARROW_X)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); constraintAxis = Vector3f.UNIT_X; // move only X } else if (pickedMarker.equals(ARROW_Y)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); constraintAxis = Vector3f.UNIT_Y; // move only Y } else if (pickedMarker.equals(ARROW_Z)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); constraintAxis = Vector3f.UNIT_Z; // move only Z } startPosition = toolController.getSelectedSpatial().getLocalTranslation().clone(); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java index d7075b9fe..6eeb723a6 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java @@ -5,6 +5,7 @@ */ package com.jme3.gde.scenecomposer.tools; +import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.SceneEditTool; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; @@ -35,10 +36,6 @@ public class PickManager { protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 - public enum TransformationType { - - local, global, camera - } public PickManager() { float size = 1000; @@ -55,7 +52,7 @@ public class PickManager { spatial = null; } - public void initiatePick(Spatial selectedSpatial, Quaternion planeRotation, TransformationType type, Camera camera, Vector2f screenCoord) { + public void initiatePick(Spatial selectedSpatial, Quaternion planeRotation, SceneComposerToolController.TransformationType type, Camera camera, Vector2f screenCoord) { spatial = selectedSpatial; startSpatialLocation = selectedSpatial.getWorldTranslation().clone(); @@ -65,16 +62,16 @@ public class PickManager { startPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); } - public void setTransformation(Quaternion planeRotation, TransformationType type) { + public void setTransformation(Quaternion planeRotation, SceneComposerToolController.TransformationType type) { Quaternion rot = new Quaternion(); - if (type == TransformationType.local) { + if (type == SceneComposerToolController.TransformationType.local) { rot.set(spatial.getWorldRotation()); rot.multLocal(planeRotation); origineRotation = spatial.getWorldRotation().clone(); - } else if (type == TransformationType.global) { + } else if (type == SceneComposerToolController.TransformationType.global) { rot.set(planeRotation); origineRotation = new Quaternion(Quaternion.IDENTITY); - } else if (type == TransformationType.camera) { + } else if (type == SceneComposerToolController.TransformationType.camera) { rot.set(planeRotation); origineRotation = planeRotation.clone(); } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java index 343d24818..c43ae52d0 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java @@ -96,14 +96,11 @@ public class RotateTool extends SceneEditTool { } if (pickedMarker.equals(QUAD_XY)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); } else if (pickedMarker.equals(QUAD_XZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); } else if (pickedMarker.equals(QUAD_YZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, - PickManager.TransformationType.local, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); } startRotate = toolController.getSelectedSpatial().getLocalRotation().clone(); } 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 1f0b5387b..69f5bfc9c 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 @@ -97,18 +97,15 @@ public class ScaleTool extends SceneEditTool { if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), - PickManager.TransformationType.camera, camera, screenCoord); + SceneComposerToolController.TransformationType.camera, camera, screenCoord); } else if (pickedMarker.equals(ARROW_X)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, - PickManager.TransformationType.global, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); constraintAxis = Vector3f.UNIT_X; // scale only X } else if (pickedMarker.equals(ARROW_Y)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, - PickManager.TransformationType.global, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); constraintAxis = Vector3f.UNIT_Y; // scale only Y } else if (pickedMarker.equals(ARROW_Z)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, - PickManager.TransformationType.global, camera, screenCoord); + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); constraintAxis = Vector3f.UNIT_Z; // scale only Z } startScale = toolController.getSelectedSpatial().getLocalScale().clone(); From 2104edabe407cd81bcd7349c24fb8244a1a223b8 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Tue, 28 Apr 2015 22:00:31 +0200 Subject: [PATCH 122/225] SDK SceneComposer : forget to add .form after the rework of the window --- .../scenecomposer/SceneComposerTopComponent.form | 16 ++++++++++++++++ .../scenecomposer/SceneComposerTopComponent.java | 4 ---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form index 8b21fb202..7893e6c52 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form @@ -99,6 +99,22 @@ + + + + + + + + + + + + + + + + diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java index c7ecb9c87..3b8a70836 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java @@ -168,7 +168,6 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce jToolBar1.setRollover(true); transformationTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Local", "Global", "Camera" })); - transformationTypeComboBox.setMaximumSize(new java.awt.Dimension(160, 50)); transformationTypeComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { transformationTypeComboBoxActionPerformed(evt); @@ -219,7 +218,6 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce } }); jToolBar1.add(rotateButton); - rotateButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.rotateButton.AccessibleContext.accessibleDescription")); // NOI18N spatialModButtonGroup.add(scaleButton); scaleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/icon_arrow_inout.png"))); // NOI18N @@ -234,8 +232,6 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce } }); jToolBar1.add(scaleButton); - scaleButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.scaleButton.AccessibleContext.accessibleDescription")); // NOI18N - jToolBar1.add(jSeparator5); jToggleScene.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/snapScene.png"))); // NOI18N From 5c539f5656fad35c7ced2a380632c470af9f2e93 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Tue, 28 Apr 2015 22:10:07 +0200 Subject: [PATCH 123/225] SDK SceneComposer : fix scale tool, now the global option should work --- .../src/com/jme3/gde/scenecomposer/tools/ScaleTool.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 69f5bfc9c..edde41a24 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 @@ -122,7 +122,9 @@ public class ScaleTool extends SceneEditTool { lastScale = scale; toolController.getSelectedSpatial().setLocalScale(scale); } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { - Vector3f diff = pickManager.getLocalTranslation(constraintAxis); + // Get the translation in the spatial Space + Quaternion worldToSpatial = toolController.getSelectedSpatial().getWorldRotation().inverse(); + Vector3f diff = pickManager.getTranslation(worldToSpatial.mult(constraintAxis)); diff.multLocal(0.5f); Vector3f scale = startScale.add(diff); lastScale = scale; From a531794c669e34e76c6eec7de431495c095b77f9 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Tue, 28 Apr 2015 22:27:12 +0200 Subject: [PATCH 124/225] SDK SceneComposer : - added tooltip for the new jComboBox - modified tooltip for scale and rotate tools : removed (in-development) --- .../src/com/jme3/gde/scenecomposer/Bundle.properties | 6 ++++-- .../jme3/gde/scenecomposer/SceneComposerTopComponent.form | 8 ++++++++ .../jme3/gde/scenecomposer/SceneComposerTopComponent.java | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties index 86f1414f9..43469051d 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties @@ -44,8 +44,8 @@ SceneComposerTopComponent.scaleButton.text= SceneComposerTopComponent.selectButton.text= SceneComposerTopComponent.selectButton.toolTipText=Select SceneComposerTopComponent.moveButton.toolTipText=Move -SceneComposerTopComponent.rotateButton.toolTipText=Rotate (in-development) -SceneComposerTopComponent.scaleButton.toolTipText=Scale (in-development) +SceneComposerTopComponent.rotateButton.toolTipText=Rotate +SceneComposerTopComponent.scaleButton.toolTipText=Scale SceneComposerTopComponent.sceneInfoPanel.border.title=no scene loaded SceneComposerTopComponent.jLabel5.text=Effects : SceneComposerTopComponent.emitButton.toolTipText=Emit all particles of all particle emitters from the selected Node @@ -63,3 +63,5 @@ SceneComposerTopComponent.jToggleScene.toolTipText=Snap to Scene SceneComposerTopComponent.jToggleGrid.toolTipText=Snap to Grid SceneComposerTopComponent.jToggleSelectGeom.toolTipText=Select Geometries SceneComposerTopComponent.jToggleSelectTerrain.toolTipText=Select Terrain +SceneComposerTopComponent.scaleButton.AccessibleContext.accessibleDescription=Scale +SceneComposerTopComponent.transformationTypeComboBox.toolTipText=Choose the transformation type used by tools. diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form index 7893e6c52..fd4686f24 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form @@ -108,6 +108,9 @@ + + + @@ -200,6 +203,11 @@ + + + + + diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java index 3b8a70836..b06e126dc 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java @@ -168,6 +168,7 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce jToolBar1.setRollover(true); transformationTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Local", "Global", "Camera" })); + transformationTypeComboBox.setToolTipText(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.transformationTypeComboBox.toolTipText")); // NOI18N transformationTypeComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { transformationTypeComboBoxActionPerformed(evt); @@ -232,6 +233,8 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce } }); jToolBar1.add(scaleButton); + scaleButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.scaleButton.AccessibleContext.accessibleDescription")); // NOI18N + jToolBar1.add(jSeparator5); jToggleScene.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/scenecomposer/snapScene.png"))); // NOI18N From 8b8425ba68bbe4f87c580cf8d6171388514b18ac Mon Sep 17 00:00:00 2001 From: Maselbas Date: Tue, 28 Apr 2015 23:55:05 +0200 Subject: [PATCH 125/225] SDK SceneComposer : fixed camera transformation type now works for move and scale tool, but act realy weird with rotate tool. --- .../jme3/gde/scenecomposer/tools/PickManager.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java index 6eeb723a6..fc0032edb 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java @@ -31,7 +31,8 @@ public class PickManager { private Quaternion origineRotation; private final Node plane; private Spatial spatial; - + private SceneComposerToolController.TransformationType transformationType; + protected static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 @@ -64,14 +65,15 @@ public class PickManager { public void setTransformation(Quaternion planeRotation, SceneComposerToolController.TransformationType type) { Quaternion rot = new Quaternion(); - if (type == SceneComposerToolController.TransformationType.local) { + transformationType = type; + if (transformationType == SceneComposerToolController.TransformationType.local) { rot.set(spatial.getWorldRotation()); rot.multLocal(planeRotation); origineRotation = spatial.getWorldRotation().clone(); - } else if (type == SceneComposerToolController.TransformationType.global) { + } else if (transformationType == SceneComposerToolController.TransformationType.global) { rot.set(planeRotation); origineRotation = new Quaternion(Quaternion.IDENTITY); - } else if (type == SceneComposerToolController.TransformationType.camera) { + } else if (transformationType == SceneComposerToolController.TransformationType.camera) { rot.set(planeRotation); origineRotation = planeRotation.clone(); } @@ -79,6 +81,10 @@ public class PickManager { } public boolean updatePick(Camera camera, Vector2f screenCoord) { + if(transformationType == SceneComposerToolController.TransformationType.camera){ + origineRotation = camera.getRotation(); + plane.setLocalRotation(camera.getRotation()); + } finalPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); return finalPickLoc != null; } From 8797bda5efb63c18b4e8a74d4e54e6357f31f913 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Tue, 28 Apr 2015 23:51:47 -0400 Subject: [PATCH 126/225] Maven: remove unneeded properties Those are determined automatically by gradle, no need to specify explicitly. --- common.gradle | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/common.gradle b/common.gradle index 9d65f7338..0852470e4 100644 --- a/common.gradle +++ b/common.gradle @@ -5,15 +5,15 @@ apply plugin: 'java' apply plugin: 'maven' -String mavenGroupId = 'com.jme3' -String mavenVersion = jmeVersion + '-' + jmeVersionTag //'-SNAPSHOT' +group = 'com.jme3' +version = jmeVersion + '-' + jmeVersionTag sourceCompatibility = '1.6' [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' repositories { mavenCentral() - maven{ + maven { url "http://nifty-gui.sourceforge.net/nifty-maven-repo" } } @@ -23,11 +23,6 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.10' } -String mavenArtifactId = name - -group = mavenGroupId -version = mavenVersion - javadoc { failOnError = false options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED @@ -60,14 +55,6 @@ artifacts { } } -configure(install.repositories.mavenInstaller) { - pom.project { - groupId = mavenGroupId - artifactId = mavenArtifactId - version = mavenVersion - } -} - task createFolders(description: 'Creates the source folders if they do not exist.') doLast { // sourceSets*.allSource*.srcDirs*.each { File srcDir -> // if (!srcDir.isDirectory()) { From 2bdd4d5a32ba3a6b24984c8917b392688a1b01ce Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Tue, 28 Apr 2015 23:59:49 -0400 Subject: [PATCH 127/225] Maven: enable maven artifact publishing Currently to local repo only. --- common.gradle | 38 ++++++++++++++++++++++++++++++++++++++ gradle.properties | 11 +++++++++++ 2 files changed, 49 insertions(+) diff --git a/common.gradle b/common.gradle index 0852470e4..6af4c664f 100644 --- a/common.gradle +++ b/common.gradle @@ -4,6 +4,7 @@ apply plugin: 'java' apply plugin: 'maven' +apply plugin: 'maven-publish' group = 'com.jme3' version = jmeVersion + '-' + jmeVersionTag @@ -55,6 +56,43 @@ artifacts { } } +publishing { + publications { + maven(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + + pom.withXml { + asNode().children().last() + { + resolveStrategy = Closure.DELEGATE_FIRST + name POM_NAME + description POM_DESCRIPTION + url POM_URL + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEVELOPER_CONNECTION + } + licenses { + license { + name POM_LICENSE_NAME + url POM_LICENSE_URL + distribution POM_LICENSE_DISTRIBUTION + } + } + } + } + } + } + + repositories { + maven { + url "${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo + } + } +} + task createFolders(description: 'Creates the source folders if they do not exist.') doLast { // sourceSets*.allSource*.srcDirs*.each { File srcDir -> // if (!srcDir.isDirectory()) { diff --git a/gradle.properties b/gradle.properties index 00429d65a..ccc3ed460 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,3 +23,14 @@ bulletZipFile = bullet.zip # Path for downloading NetBeans Base netbeansUrl = http://download.netbeans.org/netbeans/8.0.2/final/zip/netbeans-8.0.2-201411181905-javase.zip + +# POM settings +POM_NAME=jMonkeyEngine +POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers +POM_URL=http://jmonkeyengine.org +POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine +POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git +POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git +POM_LICENSE_NAME=New BSD (3-clause) License +POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause +POM_LICENSE_DISTRIBUTION=repo From df96ab4f14b6ef2ec4dfcf57d7b74651735e05d1 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Wed, 29 Apr 2015 21:52:25 +0200 Subject: [PATCH 128/225] SDK SceneComposer : fix a warning at the build time --- .../SceneComposerToolController.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java index 2ad04aa8d..50b6d5cfe 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java @@ -357,7 +357,7 @@ public class SceneComposerToolController extends SceneToolController { public void setTransformationType(String type) { if(type != null){ if(type.equals("Local")){ - setTransformationType(transformationType.local); + setTransformationType(TransformationType.local); } else if(type.equals("Global")){ setTransformationType(TransformationType.global); } else if(type.equals("Camera")){ @@ -365,15 +365,9 @@ public class SceneComposerToolController extends SceneToolController { } } } - /** - * @return the transformationType - */ - public TransformationType getTransformationType() { - return transformationType; - } /** - * @param transformationType the transformationType to set + * @param type the transformationType to set */ public void setTransformationType(TransformationType type) { if(type != this.transformationType){ @@ -385,6 +379,13 @@ public class SceneComposerToolController extends SceneToolController { } } + /** + * @return the transformationType + */ + public TransformationType getTransformationType() { + return transformationType; + } + /** * A marker on the screen that shows where a point light or * a spot light is. This marker is not part of the scene, From 7393f7916579cf26b7bbdc060756e37bc2c6d61f Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Wed, 29 Apr 2015 23:56:56 -0400 Subject: [PATCH 129/225] AudioSource: add method to get playback time As was requested on the forum, getting playback time / position is needed to perform proper audio / video synchronization. --- .../AndroidMediaPlayerAudioRenderer.java | 5 ++ .../main/java/com/jme3/audio/AudioNode.java | 8 +++ .../java/com/jme3/audio/AudioRenderer.java | 1 + .../main/java/com/jme3/audio/AudioSource.java | 5 ++ .../main/java/com/jme3/audio/AudioStream.java | 13 ++++ .../jme3/audio/openal/ALAudioRenderer.java | 60 +++++++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java index 7ed04658e..394cc257b 100644 --- a/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java @@ -525,4 +525,9 @@ public class AndroidMediaPlayerAudioRenderer implements AudioRenderer, @Override public void deleteFilter(Filter filter) { } + + @Override + public float getSourcePlaybackTime(AudioSource src) { + throw new UnsupportedOperationException("Not supported yet."); + } } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 0e75db9fe..c4dcfabf0 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -409,6 +409,14 @@ public class AudioNode extends Node implements AudioSource { play(); } } + + @Override + public float getPlaybackTime() { + if (channel >= 0) + return getRenderer().getSourcePlaybackTime(this); + else + return 0; + } public Vector3f getPosition() { return getWorldTranslation(); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java index 78ea88e91..695999e48 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java @@ -59,6 +59,7 @@ public interface AudioRenderer { public void updateSourceParam(AudioSource src, AudioParam param); public void updateListenerParam(Listener listener, ListenerParam param); + public float getSourcePlaybackTime(AudioSource src); public void deleteFilter(Filter filter); public void deleteAudioData(AudioData ad); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java index 3aa23b78d..75a4e70f9 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java @@ -95,6 +95,11 @@ public interface AudioSource { * @return the time offset in the sound sample when to start playing. */ public float getTimeOffset(); + + /** + * @return the current playback position of the source in seconds. + */ + public float getPlaybackTime(); /** * @return The velocity of the audio source. diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java index f7ff4c04b..598ae189c 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java @@ -54,6 +54,8 @@ public class AudioStream extends AudioData implements Closeable { protected boolean eof = false; protected int[] ids; + protected int unqueuedBuffersBytes = 0; + public AudioStream() { super(); } @@ -196,10 +198,21 @@ public class AudioStream extends AudioData implements Closeable { return in instanceof SeekableStream; } + public int getUnqueuedBufferBytes() { + return unqueuedBuffersBytes; + } + + public void setUnqueuedBufferBytes(int unqueuedBuffers) { + this.unqueuedBuffersBytes = unqueuedBuffers; + } + public void setTime(float time) { if (in instanceof SeekableStream) { ((SeekableStream) in).setTime(time); eof = false; + + // TODO: when we actually support seeking, this will need to be properly set. + unqueuedBuffersBytes = 0; } else { throw new IllegalStateException( "Cannot use setTime on a stream that " diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java index c3ccea741..62f04018a 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java @@ -301,6 +301,58 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { f.clearUpdateNeeded(); } + @Override + public float getSourcePlaybackTime(AudioSource src) { + checkDead(); + synchronized (threadLock) { + if (audioDisabled) { + return 0; + } + + // See comment in updateSourceParam(). + if (src.getChannel() < 0) { + return 0; + } + + int id = channels[src.getChannel()]; + AudioData data = src.getAudioData(); + int playbackOffsetBytes = 0; + + if (data instanceof AudioStream) { + // Because audio streams are processed in buffer chunks, + // we have to compute the amount of time the stream was already + // been playing based on the number of buffers that were processed. + AudioStream stream = (AudioStream) data; + + // NOTE: the assumption is that all enqueued buffers are the same size. + // this is currently enforced by fillBuffer(). + + // The number of unenqueued bytes that the decoder thread + // keeps track of. + int unqueuedBytes = stream.getUnqueuedBufferBytes(); + + // Additional processed buffers that the decoder thread + // did not unenqueue yet (it only updates 20 times per second). + int unqueuedBytesExtra = al.alGetSourcei(id, AL_BUFFERS_PROCESSED) * BUFFER_SIZE; + + // Total additional bytes that need to be considered. + playbackOffsetBytes = unqueuedBytes; // + unqueuedBytesExtra; + } + + // Add byte offset from source (for both streams and buffers) + playbackOffsetBytes += al.alGetSourcei(id, AL_BYTE_OFFSET); + + // Compute time value from bytes + // E.g. for 44100 source with 2 channels and 16 bits per sample: + // (44100 * 2 * 16 / 8) = 176400 + int bytesPerSecond = (data.getSampleRate() * + data.getChannels() * + data.getBitsPerSample() / 8); + + return (float)playbackOffsetBytes / bytesPerSecond; + } + } + public void updateSourceParam(AudioSource src, AudioParam param) { checkDead(); synchronized (threadLock) { @@ -648,6 +700,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean looping) { boolean success = false; int processed = al.alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); + int unqueuedBufferBytes = 0; for (int i = 0; i < processed; i++) { int buffer; @@ -656,6 +709,11 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { al.alSourceUnqueueBuffers(sourceId, 1, ib); buffer = ib.get(0); + // XXX: assume that reading from AudioStream always + // gives BUFFER_SIZE amount of bytes! This might not always + // be the case... + unqueuedBufferBytes += BUFFER_SIZE; + boolean active = fillBuffer(stream, buffer); if (!active && !stream.isEOF()) { @@ -682,6 +740,8 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { break; } } + + stream.setUnqueuedBufferBytes(stream.getUnqueuedBufferBytes() + unqueuedBufferBytes); return success; } From a4e1aa450ddebf6d5b12feb11be6dd82b1ff7c98 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 30 Apr 2015 09:57:46 -0400 Subject: [PATCH 130/225] README: Add build status image --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b3f993569..29161ffa4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -jMonkeyEngine +jMonkeyEngine ============= +[![Build Status](https://travis-ci.org/jMonkeyEngine/jmonkeyengine.svg?branch=master)](https://travis-ci.org/jMonkeyEngine/jmonkeyengine) + jMonkeyEngine is a 3D game engine for adventurous Java developers. It’s open source, cross platform and cutting edge. And it is all beautifully documented. The 3.0 branch is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll be frequently submitting stable 3.0.x updates until the major 3.1 version arrives. The engine is used by several commercial game studios and computer-science courses. Here's a taste: From a913d8e0c29b6c39a490ad88ac4c1036e9dfccad Mon Sep 17 00:00:00 2001 From: Maselbas Date: Thu, 30 Apr 2015 19:35:39 +0200 Subject: [PATCH 131/225] SDK Scenecomposer : now right clicking will cancel current transformation for the Scale, Rotate and Move Tool, looks like the selectTools shortcut - improved these tool feedback, its easier to begin the tranformation as its start on the first click instead of the first dragg --- .../gde/scenecomposer/tools/MoveTool.java | 101 ++++++++++------- .../gde/scenecomposer/tools/RotateTool.java | 79 +++++++------ .../gde/scenecomposer/tools/ScaleTool.java | 106 ++++++++++-------- 3 files changed, 164 insertions(+), 122 deletions(-) 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 a553f35f2..b3a48a6f6 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 @@ -60,11 +60,44 @@ public class MoveTool extends SceneEditTool { wasDragging = false; } pickManager.reset(); + } else { + if (toolController.getSelectedSpatial() == null) { + return; + } + + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { + return; + } + + if (pickedMarker.equals(QUAD_XY)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_XZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(ARROW_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_X; // move only X + } else if (pickedMarker.equals(ARROW_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Y; // move only Y + } else if (pickedMarker.equals(ARROW_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Z; // move only Z + } + startPosition = toolController.getSelectedSpatial().getLocalTranslation().clone(); + wasDragging = true; + } } } @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + cancel(); + } } @Override @@ -89,58 +122,40 @@ public class MoveTool extends SceneEditTool { wasDragging = false; } pickManager.reset(); - return; - } - - if (toolController.getSelectedSpatial() == null) { - return; - } - - if (pickedMarker == null) { - pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedMarker == null) { + } else if (wasDragging == true) { + if (!pickManager.updatePick(camera, screenCoord)) { return; } + Vector3f diff = Vector3f.ZERO; + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + diff = pickManager.getTranslation(); - if (pickedMarker.equals(QUAD_XY)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); - } else if (pickedMarker.equals(QUAD_XZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); - } else if (pickedMarker.equals(QUAD_YZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); - } else if (pickedMarker.equals(ARROW_X)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); - constraintAxis = Vector3f.UNIT_X; // move only X - } else if (pickedMarker.equals(ARROW_Y)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); - constraintAxis = Vector3f.UNIT_Y; // move only Y - } else if (pickedMarker.equals(ARROW_Z)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); - constraintAxis = Vector3f.UNIT_Z; // move only Z + } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { + diff = pickManager.getTranslation(constraintAxis); } - startPosition = toolController.getSelectedSpatial().getLocalTranslation().clone(); - - } - if (!pickManager.updatePick(camera, screenCoord)) { - return; - } - Vector3f diff = Vector3f.ZERO; - if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { - diff = pickManager.getTranslation(); - - } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { - diff = pickManager.getTranslation(constraintAxis); + Vector3f position = startPosition.add(diff); + lastPosition = position; + toolController.getSelectedSpatial().setLocalTranslation(position); + updateToolsTransformation(); } - Vector3f position = startPosition.add(diff); - lastPosition = position; - toolController.getSelectedSpatial().setLocalTranslation(position); - updateToolsTransformation(); - - wasDragging = true; } @Override public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); + } + } + + private void cancel() { + if (wasDragging) { + wasDragging = false; + toolController.getSelectedSpatial().setLocalTranslation(startPosition); + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + constraintAxis = Vector3f.UNIT_XYZ; // no constraint + pickManager.reset(); + } } protected class MoveUndo extends AbstractUndoableSceneEdit { diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java index c43ae52d0..58c69f516 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java @@ -53,12 +53,36 @@ public class RotateTool extends SceneEditTool { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); wasDragging = false; } + pickManager.reset(); + } else { + if (toolController.getSelectedSpatial() == null) { + return; + } + + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { + return; + } + + if (pickedMarker.equals(QUAD_XY)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_XZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + } else if (pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + } + startRotate = toolController.getSelectedSpatial().getLocalRotation().clone(); + wasDragging = true; + } } } @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - + if (pressed) { + cancel(); + } } @Override @@ -67,12 +91,12 @@ public class RotateTool extends SceneEditTool { highlightAxisMarker(camera, screenCoord, axisPickType); } else { pickedMarker = null; + pickManager.reset(); } } @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - if (!pressed) { setDefaultAxisMarkerColors(); pickedMarker = null; // mouse released, reset selection @@ -82,45 +106,36 @@ public class RotateTool extends SceneEditTool { actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); wasDragging = false; } - return; - } - - if (toolController.getSelectedSpatial() == null) { - return; - } - - if (pickedMarker == null) { - pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedMarker == null) { + pickManager.reset(); + } else if (wasDragging) { + if (!pickManager.updatePick(camera, screenCoord)) { return; } - if (pickedMarker.equals(QUAD_XY)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); - } else if (pickedMarker.equals(QUAD_XZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); - } else if (pickedMarker.equals(QUAD_YZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + Quaternion rotation = startRotate.mult(pickManager.getLocalRotation()); + toolController.getSelectedSpatial().setLocalRotation(rotation); + lastRotate = rotation; } - startRotate = toolController.getSelectedSpatial().getLocalRotation().clone(); - } - if (!pickManager.updatePick(camera, screenCoord)) { - return; - } - - if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { - Quaternion rotation = startRotate.mult(pickManager.getLocalRotation()); - toolController.getSelectedSpatial().setLocalRotation(rotation); - lastRotate = rotation; + updateToolsTransformation(); } - updateToolsTransformation(); - wasDragging = true; } @Override - public - void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); + } + } + private void cancel() { + if (wasDragging) { + wasDragging = false; + toolController.getSelectedSpatial().setLocalRotation(startRotate); + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + pickManager.reset(); + } } private class ScaleUndo extends AbstractUndoableSceneEdit { 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 edde41a24..a34478372 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 @@ -39,7 +39,7 @@ public class ScaleTool extends SceneEditTool { @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); - pickManager = Lookup.getDefault().lookup(PickManager.class); + pickManager = Lookup.getDefault().lookup(PickManager.class); displayPlanes(); } @@ -54,12 +54,40 @@ public class ScaleTool extends SceneEditTool { wasDragging = false; } pickManager.reset(); + } else { + if (toolController.getSelectedSpatial() == null) { + return; + } + if (pickedMarker == null) { + pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); + if (pickedMarker == null) { + return; + } + + if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), + SceneComposerToolController.TransformationType.camera, camera, screenCoord); + } else if (pickedMarker.equals(ARROW_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_X; // scale only X + } else if (pickedMarker.equals(ARROW_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Y; // scale only Y + } else if (pickedMarker.equals(ARROW_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + constraintAxis = Vector3f.UNIT_Z; // scale only Z + } + startScale = toolController.getSelectedSpatial().getLocalScale().clone(); + wasDragging = true; + } } } @Override public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - + if (pressed) { + cancel(); + } } @Override @@ -83,61 +111,45 @@ public class ScaleTool extends SceneEditTool { wasDragging = false; } pickManager.reset(); - return; - } - - if (toolController.getSelectedSpatial() == null) { - return; - } - if (pickedMarker == null) { - pickedMarker = pickAxisMarker(camera, screenCoord, axisPickType); - if (pickedMarker == null) { + } else if (wasDragging) { + if (!pickManager.updatePick(camera, screenCoord)) { return; } - if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), - SceneComposerToolController.TransformationType.camera, camera, screenCoord); - } else if (pickedMarker.equals(ARROW_X)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); - constraintAxis = Vector3f.UNIT_X; // scale only X - } else if (pickedMarker.equals(ARROW_Y)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); - constraintAxis = Vector3f.UNIT_Y; // scale only Y - } else if (pickedMarker.equals(ARROW_Z)) { - pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); - constraintAxis = Vector3f.UNIT_Z; // scale only Z + constraintAxis = pickManager.getStartOffset().normalize(); + float diff = pickManager.getTranslation(constraintAxis).dot(constraintAxis); + diff *= 0.5f; + Vector3f scale = startScale.add(new Vector3f(diff, diff, diff)); + lastScale = scale; + toolController.getSelectedSpatial().setLocalScale(scale); + } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { + // Get the translation in the spatial Space + Quaternion worldToSpatial = toolController.getSelectedSpatial().getWorldRotation().inverse(); + Vector3f diff = pickManager.getTranslation(worldToSpatial.mult(constraintAxis)); + diff.multLocal(0.5f); + Vector3f scale = startScale.add(diff); + lastScale = scale; + toolController.getSelectedSpatial().setLocalScale(scale); } - startScale = toolController.getSelectedSpatial().getLocalScale().clone(); - } - - if (!pickManager.updatePick(camera, screenCoord)) { - return; + updateToolsTransformation(); } - if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { - constraintAxis = pickManager.getStartOffset().normalize(); - float diff = pickManager.getTranslation(constraintAxis).dot(constraintAxis); - diff *= 0.5f; - Vector3f scale = startScale.add(new Vector3f(diff, diff, diff)); - lastScale = scale; - toolController.getSelectedSpatial().setLocalScale(scale); - } else if (pickedMarker.equals(ARROW_X) || pickedMarker.equals(ARROW_Y) || pickedMarker.equals(ARROW_Z)) { - // Get the translation in the spatial Space - Quaternion worldToSpatial = toolController.getSelectedSpatial().getWorldRotation().inverse(); - Vector3f diff = pickManager.getTranslation(worldToSpatial.mult(constraintAxis)); - diff.multLocal(0.5f); - Vector3f scale = startScale.add(diff); - lastScale = scale; - toolController.getSelectedSpatial().setLocalScale(scale); - } - updateToolsTransformation(); - - wasDragging = true; } @Override public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); + } + } + private void cancel() { + if (wasDragging) { + wasDragging = false; + toolController.getSelectedSpatial().setLocalScale(startScale); + setDefaultAxisMarkerColors(); + pickedMarker = null; // mouse released, reset selection + pickManager.reset(); + } } private class ScaleUndo extends AbstractUndoableSceneEdit { From 57dbf384a260aaa83903e76f21f38d87ec997114 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Fri, 1 May 2015 02:15:41 -0400 Subject: [PATCH 132/225] Modified the DefaultServer to send a second client info message to indicate that all of the local hosted services have been notified about the new connection. Modified DefaultClient to wait to start its services until it has seen this second message. Client services may want to send things to the server during their start() method but it's important that things like the serializer registry service has already processed its messages or any sends might fail. The client generally has the luxury of being able to register handlers/listeners/etc during initialize where as the server must do this when the connection arrives. So it seems reasonable to delay client service start() until all of the server-side hosted services have had a chance to initialize themselves. --- .../com/jme3/network/base/DefaultClient.java | 31 ++++++++++++++----- .../com/jme3/network/base/DefaultServer.java | 9 +++++- 2 files changed, 31 insertions(+), 9 deletions(-) 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 c0cc2e616..b297a933b 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 @@ -177,6 +177,10 @@ public class DefaultClient implements Client continue; send(ch, reg, false); } + } + + public boolean isStarted() { + return isRunning; } protected void waitForConnected() @@ -351,13 +355,16 @@ 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 ); } } + + protected void startServices() + { + // Let the services know we are finally started + services.start(); + } protected void fireDisconnected( DisconnectInfo info ) { @@ -416,11 +423,19 @@ public class DefaultClient implements Client // Pull off the connection management messages we're // interested in and then pass on the rest. if( m instanceof ClientRegistrationMessage ) { - // Then we've gotten our real id - this.id = (int)((ClientRegistrationMessage)m).getId(); - log.log( Level.FINE, "Connection established, id:{0}.", this.id ); - connecting.countDown(); - fireConnected(); + ClientRegistrationMessage crm = (ClientRegistrationMessage)m; + // See if it has a real ID + if( crm.getId() >= 0 ) { + // Then we've gotten our real id + this.id = (int)crm.getId(); + log.log( Level.FINE, "Connection established, id:{0}.", this.id ); + connecting.countDown(); + fireConnected(); + } else { + // Else it's a message letting us know that the + // hosted services have been started + startServices(); + } return; } else if( m instanceof ChannelInfoMessage ) { // This is an interum step in the connection process and 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 0a9ac0ef1..97e36134e 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 @@ -412,7 +412,14 @@ public class DefaultServer implements Server // Now we can notify the listeners about the // new connection. - fireConnectionAdded( addedConnection ); + fireConnectionAdded( addedConnection ); + + // Send a second registration message with an invalid ID + // to let the connection know that it can start its services + m = new ClientRegistrationMessage(); + m.setId(-1); + m.setReliable(true); + addedConnection.send(m); } } From e000d83ae7a1bd0976ff36eff48bddac64a811cb Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Fri, 1 May 2015 02:24:16 -0400 Subject: [PATCH 133/225] Fixed a stack overflow exception if one println()'ed a service... since most service managers print their services in their own toString(). --- .../src/main/java/com/jme3/network/service/AbstractService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 84df3d81b..370b1c5af 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -106,6 +106,6 @@ public abstract class AbstractService implements Servi @Override public String toString() { - return getClass().getName() + "[serviceManager=" + serviceManager + "]"; + return getClass().getName() + "[serviceManager.class=" + serviceManager.getClass() + "]"; } } From a77ed5277790ad693cf77c5886baadaa0499309d Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Fri, 1 May 2015 02:24:52 -0400 Subject: [PATCH 134/225] Added getServer() and getClient() convenience methods. --- .../jme3/network/service/AbstractClientService.java | 10 ++++++++++ .../jme3/network/service/AbstractHostedService.java | 9 +++++++++ 2 files changed, 19 insertions(+) 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 index bd1836451..d1a848dac 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java @@ -32,6 +32,7 @@ package com.jme3.network.service; +import com.jme3.network.Client; /** * Convenient base class for ClientServices providing some default ClientService @@ -48,4 +49,13 @@ public abstract class AbstractClientService extends AbstractService Date: Fri, 1 May 2015 02:25:24 -0400 Subject: [PATCH 135/225] Added a getRpcConnection() method to expose the underlying RPC support. --- .../com/jme3/network/service/rpc/RpcClientService.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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 index d9ea134e1..a74a7c7d1 100644 --- 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 @@ -60,6 +60,15 @@ public class RpcClientService extends AbstractClientService { public RpcClientService() { } + /** + * Returns the underlying RPC connection for use by other + * services that may require a more generic non-client/server + * specific RPC object with which to interact. + */ + public RpcConnection getRpcConnection() { + return rpc; + } + /** * Used internally to setup the RpcConnection and MessageDelegator. */ From 33d21c2de354374e36f85ba820d8022e79ab4b39 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Fri, 1 May 2015 02:26:33 -0400 Subject: [PATCH 136/225] Added an isStarted() method to Client. --- jme3-networking/src/main/java/com/jme3/network/Client.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 dd9873238..3ef9134df 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Client.java +++ b/jme3-networking/src/main/java/com/jme3/network/Client.java @@ -55,6 +55,12 @@ public interface Client extends MessageConnection */ public boolean isConnected(); + /** + * Returns true if this client has been started and is still + * running. + */ + public boolean isStarted(); + /** * Returns a unique ID for this client within the remote * server or -1 if this client isn't fully connected to the From 82f031cdff495d70e81b041e1bbd80c0b9dbd110 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Fri, 1 May 2015 19:25:14 +0200 Subject: [PATCH 137/225] SDK SceneComposer : fixed the rotate tool, now all works fine, Great Success ! --- .../gde/scenecomposer/tools/PickManager.java | 44 +++++++++++-------- .../gde/scenecomposer/tools/RotateTool.java | 2 +- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java index fc0032edb..22fa8478a 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java @@ -32,7 +32,7 @@ public class PickManager { private final Node plane; private Spatial spatial; private SceneComposerToolController.TransformationType transformationType; - + protected static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 @@ -57,13 +57,13 @@ public class PickManager { spatial = selectedSpatial; startSpatialLocation = selectedSpatial.getWorldTranslation().clone(); - setTransformation(planeRotation, type); + setTransformation(planeRotation, type, camera); plane.setLocalTranslation(startSpatialLocation); startPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); } - public void setTransformation(Quaternion planeRotation, SceneComposerToolController.TransformationType type) { + public void setTransformation(Quaternion planeRotation, SceneComposerToolController.TransformationType type, Camera camera) { Quaternion rot = new Quaternion(); transformationType = type; if (transformationType == SceneComposerToolController.TransformationType.local) { @@ -74,12 +74,18 @@ public class PickManager { rot.set(planeRotation); origineRotation = new Quaternion(Quaternion.IDENTITY); } else if (transformationType == SceneComposerToolController.TransformationType.camera) { - rot.set(planeRotation); - origineRotation = planeRotation.clone(); + rot.set(camera.getRotation()); + origineRotation = camera.getRotation().clone(); } plane.setLocalRotation(rot); } - + + /** + * + * @param camera + * @param screenCoord + * @return true if the the new picked location is set, else return false. + */ public boolean updatePick(Camera camera, Vector2f screenCoord) { if(transformationType == SceneComposerToolController.TransformationType.camera){ origineRotation = camera.getRotation(); @@ -131,23 +137,26 @@ public class PickManager { * @return the Quaternion rotation in the WorldSpace */ public Quaternion getRotation() { - Vector3f v1, v2; - v1 = startPickLoc.subtract(startSpatialLocation).normalize(); - v2 = finalPickLoc.subtract(startSpatialLocation).normalize(); - Vector3f axis = v1.cross(v2); - float angle = v1.angleBetween(v2); - return new Quaternion().fromAngleAxis(angle, axis); + return getRotation(Quaternion.IDENTITY); } /** - * + * * @return the Quaternion rotation in the ToolSpace */ public Quaternion getLocalRotation() { + return getRotation(origineRotation.inverse()); + } + + /** + * Get the Rotation into a specific custom space. + * @param transforme the rotation to the custom space (World to Custom space) + * @return the Rotation in the custom space + */ + public Quaternion getRotation(Quaternion transforme) { Vector3f v1, v2; - Quaternion rot = origineRotation.inverse(); - v1 = rot.mult(startPickLoc.subtract(startSpatialLocation).normalize()); - v2 = rot.mult(finalPickLoc.subtract(startSpatialLocation).normalize()); + v1 = transforme.mult(startPickLoc.subtract(startSpatialLocation).normalize()); + v2 = transforme.mult(finalPickLoc.subtract(startSpatialLocation).normalize()); Vector3f axis = v1.cross(v2); float angle = v1.angleBetween(v2); return new Quaternion().fromAngleAxis(angle, axis); @@ -178,8 +187,7 @@ public class PickManager { * @return */ public Vector3f getLocalTranslation(Vector3f axisConstrainte) { - //return plane.getWorldRotation().inverse().mult(getTranslation(axisConstrainte)); return getTranslation(origineRotation.inverse().mult(axisConstrainte)); } - + } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java index 58c69f516..21e9a56d8 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java @@ -113,7 +113,7 @@ public class RotateTool extends SceneEditTool { } if (pickedMarker.equals(QUAD_XY) || pickedMarker.equals(QUAD_XZ) || pickedMarker.equals(QUAD_YZ)) { - Quaternion rotation = startRotate.mult(pickManager.getLocalRotation()); + Quaternion rotation = startRotate.mult(pickManager.getRotation(startRotate.inverse())); toolController.getSelectedSpatial().setLocalRotation(rotation); lastRotate = rotation; } From c93c746b8b9715d2daee50c4a4b9d726bb614a31 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 1 May 2015 19:50:30 -0400 Subject: [PATCH 138/225] Travis-CI: display success notification on status change --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2e93aa23c..8a29bd1f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ branches: notifications: slack: secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" + on_success: change + on_failure: always before_install: # required libs for android build tools From c1dc81995334532d2671fe94d5052d0974fb0a44 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sat, 2 May 2015 00:35:15 -0400 Subject: [PATCH 139/225] Added the ability to put the serializer registry in "read only" mode. Modified the SerializerRegistrationMessage to put the serializer registry into read only after it compiles the message so that the server won't accidentally register messages after they've been compiled. --- .../SerializerRegistrationsMessage.java | 4 +++- .../jme3/network/serializing/Serializer.java | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) 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 index da7f2a7cf..9c3896475 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java @@ -127,7 +127,9 @@ public class SerializerRegistrationsMessage extends AbstractMessage { } compiled = list.toArray(new Registration[list.size()]); - INSTANCE = new SerializerRegistrationsMessage(compiled); + INSTANCE = new SerializerRegistrationsMessage(compiled); + + Serializer.setReadOnly(true); } public void registerAll() { diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java index a7d20da54..d4c054403 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java @@ -71,6 +71,8 @@ public abstract class Serializer { private static boolean strictRegistration = true; + private static volatile boolean locked = false; + // Registers the classes we already have serializers for. static { @@ -168,6 +170,20 @@ public abstract class Serializer { return nextAvailableId--; } + /** + * Can put the registry in a read-only state such that additional attempts + * to register classes will fail. This can be used by servers to lock the + * registry to avoid accidentally registering classes after a full registry + * set has been compiled. + */ + public static void setReadOnly( boolean b ) { + locked = b; + } + + public static boolean isReadOnly() { + return locked; + } + /** * Directly registers a class for a specific ID. Generally, use the regular * registerClass() method. This method is intended for framework code that might @@ -175,7 +191,11 @@ public abstract class Serializer { */ public static SerializerRegistration registerClassForId( short id, Class cls, Serializer serializer ) { - SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); + if( locked ) { + throw new RuntimeException("Serializer registry locked trying to register class:" + cls); + } + + SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); idRegistrations.put(id, reg); classRegistrations.put(cls, reg); From 58313c271db3e78c6e1f0470108ba3d095bbbae5 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sat, 2 May 2015 00:38:24 -0400 Subject: [PATCH 140/225] Added a better comment as to why we have to check the channels even though the negative channels would pass through as the default channels just fine. The key is avoiding UDP calls... they will get translated into a regular send. --- .../java/com/jme3/network/service/rpc/RpcConnection.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 index b78316bf3..f457e426b 100644 --- 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 @@ -115,8 +115,13 @@ public class RpcConnection { if( log.isLoggable(Level.FINEST) ) { log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); - } - if( channel >= 0 ) { + } + + // Prevent non-async messages from being send as UDP + // because there is a high probabilty that this would block + // forever waiting for a response. For async calls it's ok + // so it doesn't do the check. + if( channel >= 0 ) { connection.send(channel, msg); } else { connection.send(msg); From c1670e75098a1da8c8e5578abe1e611636cbdc8e Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sat, 2 May 2015 00:39:41 -0400 Subject: [PATCH 141/225] Added the read class ID to the bad deserialize exception. Two things can cause bad reads: 1) bad data in the stream... in which the extra info is useless or confusing. 2) unregistered classes or bad timing, either way, knowing the message type ID might be useful. --- .../src/main/java/com/jme3/network/base/MessageProtocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java index a8d0d39fd..6751ecc3f 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java @@ -181,7 +181,7 @@ public class MessageProtocol Message m = (Message)obj; messages.add(m); } catch( IOException e ) { - throw new RuntimeException( "Error deserializing object", e ); + throw new RuntimeException( "Error deserializing object, clas ID:" + buffer.getShort(0), e ); } } } From 42105f4c4ba8ced7102ae5f30aa43642d359cd0f Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sat, 2 May 2015 00:41:47 -0400 Subject: [PATCH 142/225] Just some whitespace changes. --- .../src/main/java/com/jme3/network/base/DefaultServer.java | 2 +- .../src/main/java/com/jme3/network/kernel/udp/UdpConnector.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) 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 97e36134e..2b9add2ef 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 @@ -223,7 +223,7 @@ public class DefaultServer implements Server { if( connections.isEmpty() ) return; - + ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); FilterAdapter adapter = filter == null ? null : new FilterAdapter(filter); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java index 20682ecee..bf0f7ddb1 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java @@ -113,7 +113,6 @@ public class UdpConnector implements Connector public ByteBuffer read() { checkClosed(); - try { DatagramPacket packet = new DatagramPacket( buffer, buffer.length ); sock.receive(packet); @@ -132,7 +131,6 @@ public class UdpConnector implements Connector public void write( ByteBuffer data ) { checkClosed(); - try { DatagramPacket p = new DatagramPacket( data.array(), data.position(), data.remaining(), remoteAddress ); From 0a0fdca0b4207c788087e355aea8f1bf5559de65 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:21:32 -0400 Subject: [PATCH 143/225] ImageRaster: add mipmap access & gamma correction * Remove deprecated image raster methods from JmeSystem * Allow ImageRaster to read / write to arbitrary mipmaps * Allow ImageRaster to perform conversion to / from linear color space as required --- .../main/java/com/jme3/system/JmeSystem.java | 9 ---- .../com/jme3/system/JmeSystemDelegate.java | 5 -- .../texture/image/DefaultImageRaster.java | 51 +++++++++++++++++-- .../com/jme3/texture/image/ImageRaster.java | 25 ++++++++- 4 files changed, 69 insertions(+), 21 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java index 1ec695388..51c2173fd 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java @@ -172,15 +172,6 @@ public class JmeSystem { return systemDelegate.getPlatformAssetConfigURL(); } - /** - * @deprecated Directly create an image raster via {@link DefaultImageRaster}. - */ - @Deprecated - public static ImageRaster createImageRaster(Image image, int slice) { - checkDelegate(); - return systemDelegate.createImageRaster(image, slice); - } - /** * Displays an error message to the user in whichever way the context * feels is appropriate. If this is a headless or an offscreen surface diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index a3f75bd9a..150275d46 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -132,11 +132,6 @@ public abstract class JmeSystemDelegate { return new DesktopAssetManager(null); } - @Deprecated - public final ImageRaster createImageRaster(Image image, int slice) { - return new DefaultImageRaster(image, slice); - } - public abstract void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException; public abstract void showErrorDialog(String message); diff --git a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java index 4cbfc3b57..c79a4675d 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java @@ -44,7 +44,9 @@ public class DefaultImageRaster extends ImageRaster { private final ImageCodec codec; private final int width; private final int height; + private final int offset; private final byte[] temp; + private final boolean convertToLinear; private int slice; private void rangeCheck(int x, int y) { @@ -53,13 +55,40 @@ public class DefaultImageRaster extends ImageRaster { } } - public DefaultImageRaster(Image image, int slice) { + public DefaultImageRaster(Image image, int slice, int mipMapLevel, boolean convertToLinear) { + int[] mipMapSizes = image.getMipMapSizes(); + int availableMips = mipMapSizes != null ? mipMapSizes.length : 1; + + if (mipMapLevel >= availableMips) { + throw new IllegalStateException("Cannot create image raster for mipmap level #" + mipMapLevel + ". " + + "Image only has " + availableMips + " mipmap levels."); + } + + if (image.hasMipmaps()) { + this.width = Math.max(1, image.getWidth() >> mipMapLevel); + this.height = Math.max(1, image.getHeight() >> mipMapLevel); + + int mipOffset = 0; + for (int i = 0; i < mipMapLevel; i++) { + mipOffset += mipMapSizes[i]; + } + + this.offset = mipOffset; + } else { + this.width = image.getWidth(); + this.height = image.getHeight(); + this.offset = 0; + } + this.image = image; this.slice = slice; + + // Conversion to linear only needed if image's color space is sRGB. + this.convertToLinear = convertToLinear && image.getColorSpace() == ColorSpace.sRGB; + this.buffer = image.getData(slice); this.codec = ImageCodec.lookup(image.getFormat()); - this.width = image.getWidth(); - this.height = image.getHeight(); + if (codec instanceof ByteAlignedImageCodec || codec instanceof ByteOffsetImageCodec) { this.temp = new byte[codec.bpp]; } else { @@ -86,6 +115,12 @@ public class DefaultImageRaster extends ImageRaster { public void setPixel(int x, int y, ColorRGBA color) { rangeCheck(x, y); + if (convertToLinear) { + // Input is linear, needs to be converted to sRGB before writing + // into image. + color = color.getAsSrgb(); + } + // Check flags for grayscale if (codec.isGray) { float gray = color.r * 0.27f + color.g * 0.67f + color.b * 0.06f; @@ -113,7 +148,7 @@ public class DefaultImageRaster extends ImageRaster { components[3] = Math.min( (int) (color.b * codec.maxBlue + 0.5f), codec.maxBlue); break; } - codec.writeComponents(getBuffer(), x, y, width, 0, components, temp); + codec.writeComponents(getBuffer(), x, y, width, offset, components, temp); image.setUpdateNeeded(); } @@ -128,7 +163,7 @@ public class DefaultImageRaster extends ImageRaster { public ColorRGBA getPixel(int x, int y, ColorRGBA store) { rangeCheck(x, y); - codec.readComponents(getBuffer(), x, y, width, 0, components, temp); + codec.readComponents(getBuffer(), x, y, width, offset, components, temp); if (store == null) { store = new ColorRGBA(); } @@ -169,6 +204,12 @@ public class DefaultImageRaster extends ImageRaster { store.a = 1; } } + + if (convertToLinear) { + // Input image is sRGB, need to convert to linear. + store.setAsSrgb(store.r, store.g, store.b, store.a); + } + return store; } } diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java index b4e583c35..92bbb3315 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java @@ -71,21 +71,42 @@ public abstract class ImageRaster { * @param image The image to read / write to. * @param slice Which slice to use. Only applies to 3D images, 2D image * arrays or cubemaps. + * @param mipMapLevel The mipmap level to read / write to. To access levels + * other than 0, the image must have + * {@link Image#setMipMapSizes(int[]) mipmap sizes} set. + * @param convertToLinear If true, the application expects read or written + * colors to be in linear color space (ImageRaster will + * automatically perform a conversion as needed). If false, the application expects + * colors to be in the image's native {@link Image#getColorSpace() color space}. + * @return An ImageRaster to read / write to the image. + */ + public static ImageRaster create(Image image, int slice, int mipMapLevel, boolean convertToLinear) { + return new DefaultImageRaster(image, slice, mipMapLevel, convertToLinear); + } + + /** + * Create new image reader / writer. + * + * @param image The image to read / write to. + * @param slice Which slice to use. Only applies to 3D images, 2D image + * arrays or cubemaps. + * @return An ImageRaster to read / write to the image. */ public static ImageRaster create(Image image, int slice) { - return JmeSystem.createImageRaster(image, slice); + return create(image, slice, 0, false); } /** * Create new image reader / writer for 2D images. * * @param image The image to read / write to. + * @return An ImageRaster to read / write to the image. */ public static ImageRaster create(Image image) { if (image.getData().size() > 1) { throw new IllegalStateException("Use constructor that takes slices argument to read from multislice image"); } - return JmeSystem.createImageRaster(image, 0); + return create(image, 0, 0, false); } public ImageRaster() { From 1f0c83ae7d031efa9854893b64345c176833dd0e Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:22:11 -0400 Subject: [PATCH 144/225] ColorRGBA: getAsSrgb() to return ColorRGBA --- .../main/java/com/jme3/math/ColorRGBA.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index 89f82ebbd..89df1bb2b 100644 --- a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -607,28 +607,28 @@ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable } /** - * Get the color in sRGB color space as a Vector4f + * Get the color in sRGB color space as a ColorRGBA. * * Note that linear values stored in the ColorRGBA will be gamma corrected - * and returned as a Vector4f - * the x atribute will be fed with the r channel in sRGB space - * the y atribute will be fed with the g channel in sRGB space - * the z atribute will be fed with the b channel in sRGB space - * the w atribute will be fed with the a channel + * and returned as a ColorRGBA. * - * Note that no correction will be performed on the alpha channel as it's - * conventionnally doesn't represent a color itself + * The x attribute will be fed with the r channel in sRGB space. + * The y attribute will be fed with the g channel in sRGB space. + * The z attribute will be fed with the b channel in sRGB space. + * The w attribute will be fed with the a channel. * - * @return the color in sRGB color space as a Vector4f - */ - public Vector4f getAsSrgb(){ - Vector4f srgb = new Vector4f(); - float invGama = 1f/GAMMA; - srgb.x = (float)Math.pow(r, invGama); - srgb.y = (float)Math.pow(g, invGama); - srgb.z = (float)Math.pow(b, invGama); - srgb.w = a; - + * Note that no correction will be performed on the alpha channel as it + * conventionally doesn't represent a color itself. + * + * @return the color in sRGB color space as a ColorRGBA. + */ + public ColorRGBA getAsSrgb() { + ColorRGBA srgb = new ColorRGBA(); + float invGama = 1f / GAMMA; + srgb.r = (float) Math.pow(r, invGama); + srgb.g = (float) Math.pow(g, invGama); + srgb.b = (float) Math.pow(b, invGama); + srgb.a = a; return srgb; } From e29988e30cb8ffe60ade1e3c5b2115c896909f6b Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:24:22 -0400 Subject: [PATCH 145/225] Image: fix cloning for last texture state --- jme3-core/src/main/java/com/jme3/texture/Image.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 96ab62819..52f311e98 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -375,7 +375,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { // attributes relating to GL object protected boolean mipsWereGenerated = false; protected boolean needGeneratedMips = false; - protected final LastTextureState lastTextureState = new LastTextureState(); + protected LastTextureState lastTextureState = new LastTextureState(); /** * Internal use only. @@ -490,6 +490,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { Image clone = (Image) super.clone(); clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null; clone.data = data != null ? new ArrayList(data) : null; + clone.lastTextureState = new LastTextureState(); clone.setUpdateNeeded(); return clone; } From 02c997b16580a24cd0f03420a07bb365a2eb8a74 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:25:07 -0400 Subject: [PATCH 146/225] Image: remove efficient state field --- jme3-core/src/main/java/com/jme3/texture/Image.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 52f311e98..ca9404720 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -367,7 +367,6 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { protected int width, height, depth; protected int[] mipMapSizes; protected ArrayList data; - protected transient Object efficientData; protected int multiSamples = 1; protected ColorSpace colorSpace = null; // protected int mipOffset = 0; @@ -761,8 +760,6 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { */ @Deprecated public void setEfficentData(Object efficientData){ - this.efficientData = efficientData; - setUpdateNeeded(); } /** @@ -770,7 +767,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { */ @Deprecated public Object getEfficentData(){ - return efficientData; + return null; } /** From f2e0a15edb1460b7de065f7428c7354fce691517 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:26:16 -0400 Subject: [PATCH 147/225] AssetConfig: remove duplicates from Desktop.cfg --- .../main/resources/com/jme3/asset/Desktop.cfg | 22 ------------------- .../main/resources/com/jme3/asset/General.cfg | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg b/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg index df72654a1..28727d070 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg @@ -2,26 +2,4 @@ INCLUDE com/jme3/asset/General.cfg # Desktop-specific loaders LOADER com.jme3.texture.plugins.AWTLoader : jpg, bmp, gif, png, jpeg -LOADER com.jme3.audio.plugins.OGGLoader : oggLOADER com.jme3.audio.plugins.WAVLoader : wav LOADER com.jme3.audio.plugins.OGGLoader : ogg -LOADER com.jme3.cursors.plugins.CursorLoader : ani, cur, ico -LOADER com.jme3.material.plugins.J3MLoader : j3m -LOADER com.jme3.material.plugins.J3MLoader : j3md -LOADER com.jme3.material.plugins.ShaderNodeDefinitionLoader : j3sn -LOADER com.jme3.font.plugins.BitmapFontLoader : fnt -LOADER com.jme3.texture.plugins.DDSLoader : dds -LOADER com.jme3.texture.plugins.PFMLoader : pfm -LOADER com.jme3.texture.plugins.HDRLoader : hdr -LOADER com.jme3.texture.plugins.TGALoader : tga -LOADER com.jme3.export.binary.BinaryImporter : j3o -LOADER com.jme3.export.binary.BinaryImporter : j3f -LOADER com.jme3.scene.plugins.OBJLoader : obj -LOADER com.jme3.scene.plugins.MTLLoader : mtl -LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml -LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml -LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material -LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene -LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend -LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag,geom,tsctrl,tseval, glsl, glsllib -LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx -LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba diff --git a/jme3-core/src/main/resources/com/jme3/asset/General.cfg b/jme3-core/src/main/resources/com/jme3/asset/General.cfg index c0098ffe5..c56b62146 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/General.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/General.cfg @@ -21,6 +21,6 @@ LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend -LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib +LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba From 55d3a5dd1564cfcaad3cade517588e8d1ca57117 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:27:28 -0400 Subject: [PATCH 148/225] UnshadedArray: does not require gpu shader extension --- .../src/main/resources/jme3test/texture/UnshadedArray.frag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag index 4cd92cff9..355cf6092 100644 --- a/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag +++ b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag @@ -1,5 +1,5 @@ #extension GL_EXT_texture_array : enable -#extension GL_EXT_gpu_shader4 : enable +// #extension GL_EXT_gpu_shader4 : enable uniform vec4 m_Color; @@ -8,7 +8,7 @@ uniform vec4 m_Color; #endif #ifdef HAS_COLORMAP - #if !defined(GL_EXT_texture_array) && !defined(GL_EXT_gpu_shader4) + #if !defined(GL_EXT_texture_array) #error Texture arrays are not supported, but required for this shader. #endif From 682b1f5b585885384b156d7cd1596159b4592396 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:28:26 -0400 Subject: [PATCH 149/225] MipMapGenerator: add generator that uses raster The one that uses AWT will be deprecated soon --- .../java/com/jme3/util/MipMapGenerator.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java diff --git a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java new file mode 100644 index 000000000..e7a33da3c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ImageRaster; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +public class MipMapGenerator { + + private MipMapGenerator() { + } + + public static Image scaleImage(Image inputImage, int outputWidth, int outputHeight) { + int size = outputWidth * outputHeight * inputImage.getFormat().getBitsPerPixel() / 8; + ByteBuffer buffer = BufferUtils.createByteBuffer(size); + Image outputImage = new Image(inputImage.getFormat(), + outputWidth, + outputHeight, + buffer, + inputImage.getColorSpace()); + + // Perform scaling in linear colorspace for higher quality. + // However it requires a lot of pow() calls.. + ImageRaster input = ImageRaster.create(inputImage, 0, 0, true); + ImageRaster output = ImageRaster.create(outputImage, 0, 0, true); + + float xRatio = ((float)(input.getWidth() - 1)) / output.getWidth(); + float yRatio = ((float)(input.getHeight() - 1)) / output.getHeight(); + + ColorRGBA outputColor = new ColorRGBA(); + ColorRGBA bottomLeft = new ColorRGBA(); + ColorRGBA bottomRight = new ColorRGBA(); + ColorRGBA topLeft = new ColorRGBA(); + ColorRGBA topRight = new ColorRGBA(); + + for (int y = 0; y < outputHeight; y++) { + for (int x = 0; x < outputWidth; x++) { + float x2f = x * xRatio; + float y2f = y * yRatio; + + int x2 = (int)x2f; + int y2 = (int)y2f; + + float xDiff = x2f - x2; + float yDiff = y2f - y2; + + input.getPixel(x2, y2, bottomLeft); + input.getPixel(x2 + 1, y2, bottomRight); + input.getPixel(x2, y2 + 1, topLeft); + input.getPixel(x2 + 1, y2 + 1, topRight); + + bottomLeft.multLocal( (1f - xDiff) * (1f - yDiff) ); + bottomRight.multLocal( (xDiff) * (1f - yDiff) ); + topLeft.multLocal( (1f - xDiff) * (yDiff) ); + topRight.multLocal( (xDiff) * (yDiff) ); + + outputColor.set(bottomLeft).addLocal(bottomRight) + .addLocal(topLeft).addLocal(topRight); + + output.setPixel(x, y, outputColor); + } + } + return outputImage; + } + + public static Image resizeToPowerOf2(Image original){ + int potWidth = FastMath.nearestPowerOfTwo(original.getWidth()); + int potHeight = FastMath.nearestPowerOfTwo(original.getHeight()); + int potSize = Math.max(potWidth, potHeight); + return scaleImage(original, potSize, potSize); + } + + public static void generateMipMaps(Image image){ + int width = image.getWidth(); + int height = image.getHeight(); + + Image current = image; + ArrayList output = new ArrayList(); + int totalSize = 0; + + while (height >= 1 || width >= 1){ + output.add(current.getData(0)); + totalSize += current.getData(0).capacity(); + + if (height == 1 || width == 1) { + break; + } + + height /= 2; + width /= 2; + + current = scaleImage(current, width, height); + } + + ByteBuffer combinedData = BufferUtils.createByteBuffer(totalSize); + int[] mipSizes = new int[output.size()]; + for (int i = 0; i < output.size(); i++){ + ByteBuffer data = output.get(i); + data.clear(); + combinedData.put(data); + mipSizes[i] = data.capacity(); + } + combinedData.flip(); + + // insert mip data into image + image.setData(0, combinedData); + image.setMipMapSizes(mipSizes); + } +} From 4aa32cd016a408bba9ce84206cba7e9a61a507e2 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:53:57 -0400 Subject: [PATCH 150/225] MipMapGen: resize to nearest power of 2 in both dimensions Before it was selecting the largest dimension, but OpenGL does not have such requirement. --- jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java index e7a33da3c..6bdfb6e65 100644 --- a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java @@ -100,8 +100,7 @@ public class MipMapGenerator { public static Image resizeToPowerOf2(Image original){ int potWidth = FastMath.nearestPowerOfTwo(original.getWidth()); int potHeight = FastMath.nearestPowerOfTwo(original.getHeight()); - int potSize = Math.max(potWidth, potHeight); - return scaleImage(original, potSize, potSize); + return scaleImage(original, potWidth, potHeight); } public static void generateMipMaps(Image image){ From ed61979825d5a1aa855c6ff3356afb812ec05fa5 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 2 May 2015 15:57:10 -0400 Subject: [PATCH 151/225] GLRenderer: resize images to POT if needed E.g. when using NPOT textures on iOS with mipmapping. --- .../com/jme3/renderer/opengl/GLRenderer.java | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) 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 37b7c753f..5e3eeca90 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 @@ -54,6 +54,7 @@ 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.MipMapGenerator; import com.jme3.util.NativeObjectManager; import java.nio.*; import java.util.Arrays; @@ -1428,7 +1429,7 @@ public class GLRenderer implements Renderer { // Check NPOT requirements checkNonPowerOfTwo(tex); - updateTexImageData(image, tex.getType(), 0); + updateTexImageData(image, tex.getType(), 0, false); // NOTE: For depth textures, sets nearest/no-mips mode // Required to fix "framebuffer unsupported" @@ -1965,8 +1966,10 @@ public class GLRenderer implements Renderer { * @param img The image to upload * @param type How the data in the image argument should be interpreted. * @param unit The texture slot to be used to upload the image, not important + * @param scaleToPot If true, the image will be scaled to power-of-2 dimensions + * before being uploaded. */ - public void updateTexImageData(Image img, Texture.Type type, int unit) { + public void updateTexImageData(Image img, Texture.Type type, int unit, boolean scaleToPot) { int texId = img.getId(); if (texId == -1) { // create texture @@ -2050,33 +2053,39 @@ public class GLRenderer implements Renderer { } } + Image imageForUpload; + if (scaleToPot) { + imageForUpload = MipMapGenerator.resizeToPowerOf2(img); + } else { + imageForUpload = img; + } if (target == GL.GL_TEXTURE_CUBE_MAP) { - List data = img.getData(); + List data = imageForUpload.getData(); if (data.size() != 6) { logger.log(Level.WARNING, "Invalid texture: {0}\n" + "Cubemap textures must contain 6 data units.", img); return; } for (int i = 0; i < 6; i++) { - texUtil.uploadTexture(img, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, linearizeSrgbImages); } } else if (target == GLExt.GL_TEXTURE_2D_ARRAY_EXT) { if (!caps.contains(Caps.TextureArray)) { throw new RendererException("Texture arrays not supported by graphics hardware"); } - List data = img.getData(); + List data = imageForUpload.getData(); // -1 index specifies prepare data for 2D Array - texUtil.uploadTexture(img, target, -1, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, target, -1, linearizeSrgbImages); for (int i = 0; i < data.size(); i++) { // upload each slice of 2D array in turn // this time with the appropriate index - texUtil.uploadTexture(img, target, i, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, target, i, linearizeSrgbImages); } } else { - texUtil.uploadTexture(img, target, 0, linearizeSrgbImages); + texUtil.uploadTexture(imageForUpload, target, 0, linearizeSrgbImages); } if (img.getMultiSamples() != imageSamples) { @@ -2097,9 +2106,23 @@ public class GLRenderer implements Renderer { Image image = tex.getImage(); if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { // Check NPOT requirements - checkNonPowerOfTwo(tex); + boolean scaleToPot = false; + + try { + checkNonPowerOfTwo(tex); + } catch (RendererException ex) { + if (logger.isLoggable(Level.WARNING)) { + int nextWidth = FastMath.nearestPowerOfTwo(tex.getImage().getWidth()); + int nextHeight = FastMath.nearestPowerOfTwo(tex.getImage().getHeight()); + logger.log(Level.WARNING, + "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + + "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + + " to " + nextWidth + "x" + nextHeight); + } + scaleToPot = true; + } - updateTexImageData(image, tex.getType(), unit); + updateTexImageData(image, tex.getType(), unit, scaleToPot); } int texId = image.getId(); From a43a405ca1663cff4008557bfbe7f8980f290077 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 15:01:03 -0400 Subject: [PATCH 152/225] StatsView: render as single object --- .../src/main/java/com/jme3/app/StatsView.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index 9f748fdf4..ae1106f2e 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -60,7 +60,7 @@ import com.jme3.scene.control.Control; */ public class StatsView extends Node implements Control { - private BitmapText[] labels; + private BitmapText statText; private Statistics statistics; private String[] statLabels; @@ -81,20 +81,17 @@ public class StatsView extends Node implements Control { statLabels = statistics.getLabels(); statData = new int[statLabels.length]; - labels = new BitmapText[statLabels.length]; BitmapFont font = manager.loadFont("Interface/Fonts/Console.fnt"); - for (int i = 0; i < labels.length; i++){ - labels[i] = new BitmapText(font); - labels[i].setLocalTranslation(0, labels[i].getLineHeight() * (i+1), 0); - attachChild(labels[i]); - } + statText = new BitmapText(font); + statText.setLocalTranslation(0, statText.getLineHeight() * statLabels.length, 0); + attachChild(statText); addControl(this); } public float getHeight() { - return labels[0].getLineHeight() * statLabels.length; + return statText.getLineHeight() * statText.getLineCount(); // labels[0].getLineHeight() * statLabels.length; } public void update(float tpf) { @@ -103,11 +100,14 @@ public class StatsView extends Node implements Control { return; statistics.getData(statData); - for (int i = 0; i < labels.length; i++) { - stringBuilder.setLength(0); - stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]); - labels[i].setText(stringBuilder); + stringBuilder.setLength(0); + + // Need to walk through it backwards, as the first label + // should appear at the bottom, not the top. + for (int i = statLabels.length - 1; i >= 0; i--) { + stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n'); } + statText.setText(stringBuilder); // Moved to ResetStatsState to make sure it is // done even if there is no StatsView or the StatsView From 886bbc08b061c65e37fa1df266fc38df20f8ce8e Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 15:19:30 -0400 Subject: [PATCH 153/225] MipMapGenerator: perform scaling in sRGB - Incorrect, but faster. In most cases where it is used, gamma correct pipeline isn't used and performance is of higher priority than quality. --- jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java index 6bdfb6e65..3ac6a7ecd 100644 --- a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java @@ -53,10 +53,8 @@ public class MipMapGenerator { buffer, inputImage.getColorSpace()); - // Perform scaling in linear colorspace for higher quality. - // However it requires a lot of pow() calls.. - ImageRaster input = ImageRaster.create(inputImage, 0, 0, true); - ImageRaster output = ImageRaster.create(outputImage, 0, 0, true); + ImageRaster input = ImageRaster.create(inputImage, 0, 0, false); + ImageRaster output = ImageRaster.create(outputImage, 0, 0, false); float xRatio = ((float)(input.getWidth() - 1)) / output.getWidth(); float yRatio = ((float)(input.getHeight() - 1)) / output.getHeight(); From 6760771b2044b4e0bac4f3ff134a31e4e37ecd0d Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 15:22:29 -0400 Subject: [PATCH 154/225] SPLighting: vertex lighting fixes - remove useless varyings - fix alpha from diffuse color --- .../Common/MatDefs/Light/SPLighting.frag | 12 ++++-------- .../Common/MatDefs/Light/SPLighting.vert | 18 +++++++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) 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 26b68a533..254806d87 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -17,10 +17,7 @@ varying vec3 SpecularSum; #ifndef VERTEX_LIGHTING uniform mat4 g_ViewMatrix; uniform vec4 g_LightData[NB_LIGHTS]; - varying vec3 vPos; -#else - varying vec3 specularAccum; - varying vec4 diffuseAccum; + varying vec3 vPos; #endif #ifdef DIFFUSEMAP @@ -167,10 +164,9 @@ void main(){ #endif #ifdef VERTEX_LIGHTING - gl_FragColor.rgb = AmbientSum * diffuseColor.rgb - +diffuseAccum.rgb *diffuseColor.rgb - +specularAccum.rgb * specularColor.rgb; - gl_FragColor.a=1.0; + gl_FragColor.rgb = AmbientSum.rgb * diffuseColor.rgb + + DiffuseSum.rgb * diffuseColor.rgb + + SpecularSum.rgb * specularColor.rgb; #else int i = 0; 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 6ad224d9b..1fde8e13d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -43,8 +43,6 @@ attribute vec3 inNormal; varying vec3 vBinormal; #endif #else - varying vec3 specularAccum; - varying vec4 diffuseAccum; #ifdef COLORRAMP uniform sampler2D m_ColorRamp; #endif @@ -131,14 +129,13 @@ void main(){ #endif #ifdef VERTEX_LIGHTING int i = 0; - diffuseAccum = vec4(0.0); - specularAccum = vec3(0.0); + vec3 diffuseAccum = vec3(0.0); + vec3 specularAccum = vec3(0.0); vec4 diffuseColor; vec3 specularColor; for (int i =0;i < NB_LIGHTS; i+=3){ vec4 lightColor = g_LightData[i]; vec4 lightData1 = g_LightData[i+1]; - DiffuseSum = vec4(1.0); #ifdef MATERIAL_COLORS diffuseColor = m_Diffuse * vec4(lightColor.rgb, 1.0); specularColor = m_Specular.rgb * lightColor.rgb; @@ -166,13 +163,16 @@ void main(){ vec2 light = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess); #ifdef COLORRAMP - 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; + diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor.rgb; + specularAccum += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor; #else - diffuseAccum.rgb += light.x * diffuseColor.rgb; - specularAccum.rgb += light.y * specularColor; + diffuseAccum += light.x * diffuseColor.rgb; + specularAccum += light.y * specularColor; #endif } + + DiffuseSum.rgb *= diffuseAccum.rgb; + SpecularSum.rgb *= specularAccum.rgb; #endif From 17bf0f8ab3222ac21a1d86d168a1dab90efc7b61 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 15:52:31 -0400 Subject: [PATCH 155/225] SkeletonControl: fix #207 --- .../com/jme3/animation/SkeletonControl.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index d5ef31939..4d001c365 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -347,11 +347,22 @@ public class SkeletonControl extends AbstractControl implements Cloneable { public Control cloneForSpatial(Spatial spatial) { Node clonedNode = (Node) spatial; - AnimControl ctrl = spatial.getControl(AnimControl.class); SkeletonControl clone = new SkeletonControl(); - clone.skeleton = ctrl.getSkeleton(); - + AnimControl ctrl = spatial.getControl(AnimControl.class); + if (ctrl != null) { + // AnimControl is responsible for cloning the skeleton, not + // SkeletonControl. + clone.skeleton = ctrl.getSkeleton(); + } else { + // If there's no AnimControl, create the clone ourselves. + clone.skeleton = new Skeleton(skeleton); + } + clone.hwSkinningDesired = this.hwSkinningDesired; + clone.hwSkinningEnabled = this.hwSkinningEnabled; + clone.hwSkinningSupported = this.hwSkinningSupported; + clone.hwSkinningTested = this.hwSkinningTested; + clone.setSpatial(clonedNode); // Fix attachments for the cloned node From b4baaadc79ab259006ae0fa29469d45fabbc9594 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 15:59:32 -0400 Subject: [PATCH 156/225] JmeExporter: remove useless return --- .../java/com/jme3/export/JmeExporter.java | 8 ++----- .../jme3/export/binary/BinaryExporter.java | 14 +++++------- .../java/com/jme3/export/xml/XMLExporter.java | 22 ++++++++++++------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/export/JmeExporter.java b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java index 0afd170ea..b8c3abc60 100644 --- a/jme3-core/src/main/java/com/jme3/export/JmeExporter.java +++ b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java @@ -46,22 +46,18 @@ public interface JmeExporter { * * @param object The savable to export * @param f The output stream - * @return Always returns true. If an error occurs during export, - * an exception is thrown * @throws IOException If an io exception occurs during export */ - public boolean save(Savable object, OutputStream f) throws IOException; + public void save(Savable object, OutputStream f) throws IOException; /** * Export the {@link Savable} to a file. * * @param object The savable to export * @param f The file to export to - * @return Always returns true. If an error occurs during export, - * an exception is thrown * @throws IOException If an io exception occurs during export */ - public boolean save(Savable object, File f) throws IOException; + public void save(Savable object, File f) throws IOException; /** * Returns the {@link OutputCapsule} for the given savable object. diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java index c02f7088a..b5f458697 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java @@ -168,7 +168,7 @@ public class BinaryExporter implements JmeExporter { return new BinaryExporter(); } - public boolean save(Savable object, OutputStream os) throws IOException { + public void save(Savable object, OutputStream os) throws IOException { // reset some vars aliasCount = 1; idCount = 1; @@ -286,7 +286,7 @@ public class BinaryExporter implements JmeExporter { out = null; os = null; - if (debug ) { + if (debug) { logger.fine("Stats:"); logger.log(Level.FINE, "classes: {0}", classNum); logger.log(Level.FINE, "class table: {0} bytes", classTableSize); @@ -294,8 +294,6 @@ public class BinaryExporter implements JmeExporter { logger.log(Level.FINE, "location table: {0} bytes", locationTableSize); logger.log(Level.FINE, "data: {0} bytes", location); } - - return true; } protected String getChunk(BinaryIdContentPair pair) { @@ -325,7 +323,7 @@ public class BinaryExporter implements JmeExporter { return bytes; } - public boolean save(Savable object, File f) throws IOException { + public void save(Savable object, File f) throws IOException { File parentDirectory = f.getParentFile(); if (parentDirectory != null && !parentDirectory.exists()) { parentDirectory.mkdirs(); @@ -333,11 +331,9 @@ public class BinaryExporter implements JmeExporter { FileOutputStream fos = new FileOutputStream(f); try { - return save(object, fos); + save(object, fos); } finally { - if (fos != null) { - fos.close(); - } + fos.close(); } } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java index 32138270d..0ede52884 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java @@ -40,6 +40,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; /** * Part of the jME XML IO system as introduced in the google code jmexml project. @@ -61,7 +62,8 @@ public class XMLExporter implements JmeExporter { } - public boolean save(Savable object, OutputStream f) throws IOException { + @Override + public void save(Savable object, OutputStream f) throws IOException { try { //Initialize Document when saving so we don't retain state of previous exports this.domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(), this); @@ -69,18 +71,22 @@ public class XMLExporter implements JmeExporter { DOMSerializer serializer = new DOMSerializer(); serializer.serialize(domOut.getDoc(), f); f.flush(); - return true; - } catch (Exception ex) { - IOException e = new IOException(); - e.initCause(ex); - throw e; + } catch (ParserConfigurationException ex) { + throw new IOException(ex); } } - public boolean save(Savable object, File f) throws IOException { - return save(object, new FileOutputStream(f)); + @Override + public void save(Savable object, File f) throws IOException { + FileOutputStream fos = new FileOutputStream(f); + try { + save(object, fos); + } finally { + fos.close(); + } } + @Override public OutputCapsule getCapsule(Savable object) { return domOut; } From 0178029782a35bb21edb6fc9bebc17c896eba663 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 16:02:25 -0400 Subject: [PATCH 157/225] Example to show character model cloning / export --- .../jme3/export/binary/BinaryExporter.java | 26 ++++++ .../effect/TestParticleExportingCloning.java | 23 +---- .../model/anim/TestModelExportingCloning.java | 83 +++++++++++++++++++ 3 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java index b5f458697..eb12bf908 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java @@ -31,6 +31,7 @@ */ package com.jme3.export.binary; +import com.jme3.asset.AssetManager; import com.jme3.export.FormatVersion; import com.jme3.export.JmeExporter; import com.jme3.export.Savable; @@ -167,6 +168,31 @@ public class BinaryExporter implements JmeExporter { public static BinaryExporter getInstance() { return new BinaryExporter(); } + + /** + * Saves the object into memory then loads it from memory. + * + * Used by tests to check if the persistence system is working. + * + * @param The type of savable. + * @param assetManager AssetManager to load assets from. + * @param object The object to save and then load. + * @return A new instance that has been saved and loaded from the + * original object. + */ + public static T saveAndLoad(AssetManager assetManager, T object) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + BinaryExporter exporter = new BinaryExporter(); + exporter.save(object, baos); + BinaryImporter importer = new BinaryImporter(); + importer.setAssetManager(assetManager); + return (T) importer.load(baos.toByteArray()); + } catch (IOException ex) { + // Should never happen. + throw new AssertionError(ex); + } + } public void save(Savable object, OutputStream os) throws IOException { // reset some vars diff --git a/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java b/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java index 552287f75..d245de23f 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java @@ -69,26 +69,9 @@ public class TestParticleExportingCloning extends SimpleApplication { rootNode.attachChild(emit); rootNode.attachChild(emit2); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - BinaryExporter.getInstance().save(emit, out); - - BinaryImporter imp = new BinaryImporter(); - imp.setAssetManager(assetManager); - ParticleEmitter emit3 = (ParticleEmitter) imp.load(out.toByteArray()); - - emit3.move(-3, 0, 0); - rootNode.attachChild(emit3); - } catch (IOException ex) { - ex.printStackTrace(); - } - - // Camera cam2 = cam.clone(); - // cam.setViewPortTop(0.5f); - // cam2.setViewPortBottom(0.5f); - // ViewPort vp = renderManager.createMainView("SecondView", cam2); - // viewPort.setClearEnabled(false); - // vp.attachScene(rootNode); + ParticleEmitter emit3 = BinaryExporter.saveAndLoad(assetManager, emit); + emit3.move(-3, 0, 0); + rootNode.attachChild(emit3); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java b/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java new file mode 100644 index 000000000..de162907d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.anim; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.app.SimpleApplication; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +public class TestModelExportingCloning extends SimpleApplication { + + public static void main(String[] args) { + TestModelExportingCloning app = new TestModelExportingCloning(); + app.start(); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(10f, 3f, 40f)); + cam.lookAtDirection(Vector3f.UNIT_Z.negate(), Vector3f.UNIT_Y); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + + AnimControl control; + AnimChannel channel; + + Spatial originalModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + control = originalModel.getControl(AnimControl.class); + channel = control.createChannel(); + channel.setAnim("Walk"); + rootNode.attachChild(originalModel); + + Spatial clonedModel = originalModel.clone(); + clonedModel.move(10, 0, 0); + control = clonedModel.getControl(AnimControl.class); + channel = control.createChannel(); + channel.setAnim("push"); + rootNode.attachChild(clonedModel); + + Spatial exportedModel = BinaryExporter.saveAndLoad(assetManager, originalModel); + exportedModel.move(20, 0, 0); + control = exportedModel.getControl(AnimControl.class); + channel = control.createChannel(); + channel.setAnim("pull"); + rootNode.attachChild(exportedModel); + } +} From 0eb8cbfc6c692f4ffe714e493b688e8e9d1a2262 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 16:07:50 -0400 Subject: [PATCH 158/225] StatsView: fix darken effect due to earlier change --- jme3-core/src/main/java/com/jme3/app/StatsView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index ae1106f2e..a0446e85e 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -91,7 +91,7 @@ public class StatsView extends Node implements Control { } public float getHeight() { - return statText.getLineHeight() * statText.getLineCount(); // labels[0].getLineHeight() * statLabels.length; + return statText.getLineHeight() * statLabels.length; } public void update(float tpf) { From 7057f9a1b71f16c4108a88031fb8a549d5268e33 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 20:22:45 -0400 Subject: [PATCH 159/225] SkeletonControl: enable HW skinning by default --- jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 4d001c365..b1f3d02df 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -82,7 +82,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { /** * User wishes to use hardware skinning if available. */ - private transient boolean hwSkinningDesired = false; + private transient boolean hwSkinningDesired = true; /** * Hardware skinning is currently being used. From b5d8fc250541d8066cbab4506d10f23e66067b04 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 20:24:52 -0400 Subject: [PATCH 160/225] AbstractBox: make mesh static by default --- jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java index 35f078eeb..ab5db2462 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java @@ -149,6 +149,7 @@ public abstract class AbstractBox extends Mesh { duUpdateGeometryNormals(); duUpdateGeometryTextures(); duUpdateGeometryIndices(); + setStatic(); } /** From f8dd2542b1d4c4e08f7d97e326814954b681041c Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 3 May 2015 20:25:20 -0400 Subject: [PATCH 161/225] AudioNode: disable reverb by default --- jme3-core/src/main/java/com/jme3/audio/AudioNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index c4dcfabf0..8664af6c7 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -77,7 +77,7 @@ public class AudioNode extends Node implements AudioSource { protected transient volatile AudioSource.Status status = AudioSource.Status.Stopped; protected transient volatile int channel = -1; protected Vector3f velocity = new Vector3f(); - protected boolean reverbEnabled = true; + protected boolean reverbEnabled = false; protected float maxDistance = 200; // 200 meters protected float refDistance = 10; // 10 meters protected Filter reverbFilter; From 91cf9e645cab0d156b2039bd7a7789f93f249015 Mon Sep 17 00:00:00 2001 From: Nehon Date: Mon, 4 May 2015 20:28:52 +0200 Subject: [PATCH 162/225] BatchNode safe catch of a crash when the batch node geoms don't have the same buffer types. Added a utility method in GeometryBatchFactory to align the buffers of the subgraph. --- .../main/java/com/jme3/scene/BatchNode.java | 12 +-- .../optimize/GeometryBatchFactory.java | 90 +++++++++++++++++++ 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 475cfdf71..2d603ac2c 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -583,11 +583,13 @@ public class BatchNode extends GeometryGroupNode { useTangents = true; } } else { - inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); -// for (int vert = 0; vert < geomVertCount; vert++) { -// int curGlobalVertIndex = globalVertIndex + vert; -// inBuf.copyElement(vert, outBuf, curGlobalVertIndex); -// } + if (inBuf == null) { + throw new IllegalArgumentException("Geometry " + geom.getName() + " has no " + outBuf.getBufferType() + " buffer whereas other geoms have. all geometries should have the same types of buffers.\n Try to use GeometryBatchFactory.alignBuffer() on the BatchNode before batching"); + } else if (outBuf == null) { + throw new IllegalArgumentException("Geometry " + geom.getName() + " has a " + outBuf.getBufferType() + " buffer whereas other geoms don't. all geometries should have the same types of buffers.\n Try to use GeometryBatchFactory.alignBuffer() on the BatchNode before batching"); + } else { + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); + } } } diff --git a/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java index a7668dc56..9904b3283 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java @@ -16,6 +16,7 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.*; +import java.util.logging.Level; import java.util.logging.Logger; public class GeometryBatchFactory { @@ -453,4 +454,93 @@ public class GeometryBatchFactory { mergeGeometries(geoms, outMesh); printMesh(outMesh); } + + /** + * Options to align the buffers of geometries' meshes of a sub graph + * + */ + public static enum AlignOption { + + /** + * Will remove the buffers of a type that is not on all the geometries + */ + RemoveUnalignedBuffers, + /** + * Will create missing buffers and pad with dummy data + */ + CreateMissingBuffers + } + + /** + * Will ensure that all the geometries' meshes of the n sub graph have the + * same types of buffers + * @param n the node to gather geometries from + * @param option the align options + * @see AlignOption + * + * Very experimental for now. + */ + public static void alignBuffers(Node n, AlignOption option) { + List geoms = new ArrayList(); + gatherGeoms(n, geoms); + + //gather buffer types + Map types = new EnumMap(VertexBuffer.Type.class); + Map typesCount = new EnumMap(VertexBuffer.Type.class); + for (Geometry geom : geoms) { + for (VertexBuffer buffer : geom.getMesh().getBufferList()) { + if (types.get(buffer.getBufferType()) == null) { + types.put(buffer.getBufferType(), buffer); + logger.log(Level.FINE, buffer.getBufferType().toString()); + } + Integer count = typesCount.get(buffer.getBufferType()); + if (count == null) { + count = 0; + } + count++; + typesCount.put(buffer.getBufferType(), count); + } + } + + switch (option) { + case RemoveUnalignedBuffers: + for (Geometry geom : geoms) { + + for (VertexBuffer buffer : geom.getMesh().getBufferList()) { + Integer count = typesCount.get(buffer.getBufferType()); + if (count != null && count < geoms.size()) { + geom.getMesh().clearBuffer(buffer.getBufferType()); + logger.log(Level.FINE, "removing {0} from {1}", new Object[]{buffer.getBufferType(), geom.getName()}); + + } + } + } + break; + case CreateMissingBuffers: + for (Geometry geom : geoms) { + for (VertexBuffer.Type type : types.keySet()) { + if (geom.getMesh().getBuffer(type) == null) { + VertexBuffer vb = new VertexBuffer(type); + Buffer b; + switch (type) { + case Index: + case BoneIndex: + case HWBoneIndex: + b = BufferUtils.createIntBuffer(geom.getMesh().getVertexCount() * types.get(type).getNumComponents()); + break; + case InterleavedData: + b = BufferUtils.createByteBuffer(geom.getMesh().getVertexCount() * types.get(type).getNumComponents()); + break; + default: + b = BufferUtils.createFloatBuffer(geom.getMesh().getVertexCount() * types.get(type).getNumComponents()); + } + vb.setupData(types.get(type).getUsage(), types.get(type).getNumComponents(), types.get(type).getFormat(), b); + geom.getMesh().setBuffer(vb); + logger.log(Level.FINE, "geom {0} misses buffer {1}. Creating", new Object[]{geom.getName(), type}); + } + } + } + break; + } + } } From f733b69f9f46a845645e5e9b3627d8ebd2b46c22 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 4 May 2015 11:14:02 -0400 Subject: [PATCH 163/225] Gradle: minor cleanup to build.gradle --- build.gradle | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index adb31217b..62d4ac841 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,10 @@ import org.gradle.api.artifacts.* -apply plugin: 'base' // To add "clean" task to the root project. -//apply plugin: 'java-library-distribution' +apply plugin: 'base' // This is applied to all sub projects subprojects { - // Don't add to native builds - // if(!project.name.endsWith('native')){ apply from: rootProject.file('common.gradle') - // } } task run(dependsOn: ':jme3-examples:run') { From f74ae990d481cda5767b91c184fc59c41c28a1ee Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 4 May 2015 11:17:01 -0400 Subject: [PATCH 164/225] Shadows: make sure to clear all buffers --- .../src/main/java/com/jme3/shadow/AbstractShadowRenderer.java | 2 +- .../src/main/java/com/jme3/shadow/BasicShadowRenderer.java | 2 +- jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java index 5fac9c834..5ff4ac8a7 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -424,7 +424,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable renderManager.setCamera(shadowCam, false); renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]); - renderManager.getRenderer().clearBuffers(false, true, false); + renderManager.getRenderer().clearBuffers(true, true, true); // render shadow casters to shadow map viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); diff --git a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java index bc4273db7..1410574ea 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java @@ -190,7 +190,7 @@ public class BasicShadowRenderer implements SceneProcessor { renderManager.setForcedMaterial(preshadowMat); r.setFrameBuffer(shadowFB); - r.clearBuffers(false, true, false); + r.clearBuffers(true, true, true); viewPort.getQueue().renderShadowQueue(shadowOccluders, renderManager, shadowCam, true); r.setFrameBuffer(viewPort.getOutputFrameBuffer()); diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java index 182052e13..e06b5c337 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java @@ -450,7 +450,7 @@ public class PssmShadowRenderer implements SceneProcessor { } r.setFrameBuffer(shadowFB[i]); - r.clearBuffers(false, true, false); + r.clearBuffers(true, true, true); // render shadow casters to shadow map viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true); From 9e4360cd6a3ef68a932296bbccb630167f81ab00 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 4 May 2015 11:17:54 -0400 Subject: [PATCH 165/225] Bullet-Native: update windows natives --- .../libs/native/windows/x86/bulletjme.dll | Bin 1404928 -> 1406976 bytes .../libs/native/windows/x86_64/bulletjme.dll | Bin 1772544 -> 1777152 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll b/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll index 6839705bf73d4eb4a4e0be6d987a3ba17e756555..6874ad53d77e9a640704681ebe24482ffc0dc979 100644 GIT binary patch delta 580121 zcmafc3tZI27XLneyZnTOT@>Ug4@E^q!MA1^J~k~~0~GTurm6WTH8nGFF{H$GjqYaC z!^++-Z&BB4T0WpmsjTo`>uOnIS&czO$t^09|M$$yZ&|$k|9|_$Z|2N7XU?3NIrBO* zTT)k8(pXq=rMd3DtHyZ$dE#`+PnEBGjQ_K)-{ARK#P@n$!1wiYi#?~r_Xf`@@tx}V z4BwV7r+S8n?@~{r_}(nO1vk%MOuS6$U!L1!nrPNcH*FkOXu#@Bmh=d-!(>Y77-2F! z_J@G~c|{=6JSoy-jWKnaZ8F6a#h5Y!_gRu{MUm!-n-J=1d9X6@hIwd*u;JiOplCp@iEt1OpEjSbae5IsAyB^5pDL{$MLVw*RaI2cz0n( z*Z!voZNqBKw77`S{)`OFijMcz5cJ}$fUY3u$;kG^r45dYPM1v^(9w0#8SQQfjWkna ztV(I}yIb=7p5}aiVN=0=Q|B%&J{tx1eJ$l6SxA^o4b5hgi3sBd5zB<=vdx!TTM6LW zD)VTPc?2A`F5a}P$Y_4aDJgOkLIWY>%1GH*Mcq&?sn7e}4S9Y)fg#HIg8ezf;?4Ff z%9<3_V#V?du}oGhHz3h3Py)}}BC~Rd!79ZdOA(w5k+`fuaa+ALaLyLz%_D+rMKDM) z%}_WkO3ovpIi43e5(-p!B;UV-00sMth|xDu?Mr`v2v15bWvdleqx5_H?gN3_?U7k4 zi1aB%TBESfC~TwDyNOk0d7#qX#aluIGZeuQCFIkDU}*ZJqMoKyu+#5;8k#0BG`+K6 ze+99KYtN!=nW9n>yYDBKibdIRB>DwPU}9LLcNa1EFjAJ+kYO7UD6N&nC~lQXz|DU5 zHpoC=$gsI!eMTTdfET z1_u;F*VaIyo|aupG~dHUkfZ3muxkh*L#oPMC9$Pp+~;@K zAgd~5wGVw&l@xfZ2}RoRXm(FHB4bOsfy20aYN)x#QkN21*s_! z?TBCn`x}YfJ7Kc;hTv&PA`KWOCSJv1QtpdHx9ta-wU^Q83daz^q~hnx19hNr5lv4; zb4H2TsM@R1ow$ng@<4VJx-HS1vP;?O6wN7xW0;g$6_4K<*a(^&qA6E2Mm|*v$1sum zRD8kKzz3koC7LTP%jbE8qq;`fuC{!VooU&5L{o2*vKjeQC>*2zZBcQQXDMikh-Rmv zF{#$HPT?4`tx)k(<$*Uqvw~!@ zb5%TZYhW5^Dv0Kl%V=^Gj$w*3+i0Ex%`T!Tx{M}X;TSf}rQ(q77gw6Ml4znqv$(2f zglkF664&A?0`-w=h69Fn8%j88yA=0%gbIe2bRp5ZcSJn5jZ-l zZ{Vxc(E;1+q(DiZ4(yKUx+CJ(`3{q>u;rMgsz0lkt}k;`_3!BNd0IHlq?tZLzczb> z9%1r(YWYBXeeRahs+>F^_^wUnKV?Old^J1~33>j)sse5zqIpH(N$imsm_6^2>7R#* z^vVMMHZ;Y4eA1k#`#p91)jk#zJ3B*9)BF_rp{zrfFo18SNY9yiiZ+fy*Ur?lv^Wag zL7{JZBlN;dePm!!$~cQQAW)f-tlbZxS;@;9@&Cqw zH*t#RzgLCHU>5&T29J))HT&FbNvzf}BS@3QcU+7zXto+bjUSG4uwAiQS8MNh2YV-0 z>+cV2>FN>mLlQD>g^_(V*_cZtl z8=$aeo{lM>&)rm7WiEF&EomjthrrDbvC&!vK~DjCVFM}O(}ZjqIa||N8|7V#F&{!4 zZV^H_{j^Lo1eqbztf7K@0^y_}qZOmlYK^bJ2usT6Hy{_EsLMuz4Ah zsAa$BPks$X9=1MS8y@xU?~$fFYNd5N<$|46$7@N}UB5@Nv+>%s9W+VZMQ8mJw5-V= zfeOqxeOx07&##$;S`aGur%d^fKI64=Pn8c{mv~O(s8D2m((gGb+hrDS{uN!^bCT~O zDJv7SewmlPrD`wWcY_{gr`&zO=RPpPeSXa}pQk~R#q-nO+L=93>l6MGVVm;!z%Vu< zQOk*r{!JADtv-v-2ik^2ExYr|fezD!88Ek0;0z2I)bt&J{yk$7rj>h|7wbY7m-wf! zzSK!aW5i=WBx-S~aiFA{Nd*MuUEEPdT{60#Wh7}i`YGaA!+&D4lC&)SX&F1nDw4GR zvzN-)X8Po5<%N5E?mf`c5pMYgqU+_JGd|B5{xOnAxEogM6saLxe5)jIO;?J%4be)Q z&1tq=BfR9hCce2~bZmS?Ff z!pWG`^1kj4CiYaamg$`*d9J4qiM`wB-Yvv#J}GPcD8H#aIX)!lBH@_$&PHLNQJzMq;RsU9~b!Y=UhO_A)86m6lU-o*BQY3st$QZ=fNyDoJ{3(^4Ask#BMsG!Xv6t@u9=-p z)vk<5|1Wgv)=TTms~u$8_{Zm5?B0Wxnr(vej|v{zce+p(AZMi z;G*Ge73#R$jZsqP_po2mw1w86B2CPB$l8US>1>W;lMh=4cS3RtzZFWPdg@G(8v}11 z&t~h>wJX^Dhb^h>sdTM~eRY#HiKTSax?TTS3vq3tLKF6J5yg<(G@`J1jivrdX_;Dh zX%Q8)xm@=ZHj84e0-&@CHm1=)DFTF5bk(}|#1IdSKk>ep{4B}ie%~Jy85v=c{4{(x9pH6Q~2|Yw;ivZ-zuV{~WLZRrN8x!sG&<(c;lbLo>9Jpw(_<6h@`2I|fv|2@; zFUXq~H;OooxH`v{t0@rZmmBDmmd9=zW9h*jnx#*_+Jqg&wTYOWV9Xw!bJ01=rU8PP z2$uP|IrY6zVj9iU0CZ-L%2^xVhB^(Xtp991-(Vspy18vkh%|Z>F%j$)lhMQ^h$@&= z&(;ep?_SGd=jbCXIfGc?9DR`X_E?zMbN9I7bl-${{xrolJslO7?Dtgivc_9bK_u;26Z3=IR5Ijk)~! z`hJ98gADmvCn*gKM;Bp|{lrkR3=Y4iB;Pk7hCg|g$>c9ADe#MhcOHLjI3=U|qL_Ch zjlEH%chhd|h`I2&B0WaCfkM9(=>sFv5Q3FZyob-z$7r?DNGzGBPtrD0=wI{n?8pfS zwV_4bs1Nq`8bvL3OeQo|na}T(0#Sx8kuW*4fYixvG$gIWCPYbrZ-R?|1O%aKKW!7B z0P*|)fp$<7RJ7kf6_C%nD`Ht6z6lxpwr^WoPtQK?8^hkZQSUgbd5F-1uZB+&3ALoJ zTA^R5SFz_~C|T?lJCw#TPGi~PLb)cSflAv#)zGNAV;bNrXTCm!s&Mgqy(fL|o3BsQ zZgnE%+=uC@Er^#<(_T6F?!yTBEDgzP)fObOKI^I z%n06wwecbK+BBYzewlwdIm1`D&39cI?}{i4-!|X)WIn`5BFtnSCXyyQV5X6r#&3uA zQ4KNJ6p~&1Xg1~Ibr|$FNCX!UL~G>F3Is=<-(3YeFS7rtgILEpz7}P?X$)9WZ7K=g zk*PnN7pdbY)#us5zvxQ#cXNH%G52Pa1Vw2X_yHe%!bKwn^DWT_{FM?Wt_W4a>YJ1V zHX$XIKAHc91_SBPx}RS8dsxWvC9e;f7q$9?elr|`5fEJ9Tg%a50hMe z4k_%`WqL1bzmHt(k!AXTkwK}b5qY+4suoh*{3XUTr5`2dRDjSrUx9Uj^{UF}-Frj6 zh~;`#uUMHh33g?PMYRFJOBC?ebAm7jsjO(Z9;@As4$kgau3xM5rqEG@B0e8zGQB~E zjVHcjo0jWoY|^SQ-3k5sx4F_t^uwq64IZ&gB$0?t2a zZEb_QTWqyv6rG;{7?hi;8(}&4KZv+ui6G7u;3`x>K7Z+x);SVQ=wPezTF0sJhS2LW`8`3ZnVoY&g5?R()}3^WRN2fSe>`t!M{QW3n}An8x=i|Z~Hj3=$_q>8)eVus{se4 zD*M!KCT~dL3wLjNiwa-BCmCp>>?X=m@X#Rn&&W~4MA@JIBDC!8-8L$E9*;B_h@yLe zBuGTjSIXGd$*wK;l`y_SA2`@-75(#yOwa<6%6X`}$cBFcxv^@4=3qEV-wXCi2Sy!t zco#Egx2)8=jeNf+k@}ZmWKqriT4Jz+0HtrOz(7<CfSp~Xr&!;uaXoOGp5nS>!b%@yBdh&0!mQtI`ncdsNF7GYF};!K!Ti44 z^e);VbYk|z7;|S!01vBX6|()gtoig6)DGOv3t60ru%5osv;mxwj0B5PS>dz+NJ zzw|MgUY~J0rX8;Le>0=S=%=+Uat#(3bv_|_Q?+PKj|9_L{o?TMu{k`v3$+!oZlXT% zM>0zC-#hevS}&lop6m7N%o%LedOa^Zu`e*is*WY^(b5M>=H3)9+L%x9PrHNmLwp2QLK0Fa|$7-koxlSAV*=Q z<_L~Es=%>;ze)s~GPUGUU;=6C-v{4ESAdzCW&0*)75K|s0OawFiGru!Pf^TKtB_(6 zDN~5ehK+j8Jc$B0~MsSTv) zL9ae8fz%|VSRka3Rr5AwLslcQxT{|*L%8cA8cS}8J|y~+pV8&i3|B7I?kUkzu72)A zsy+ul{24?M9EH`aq^L2Iw@QK0mS~14Wb@~ud^|i|m=G_Wj7$LkV0)gB;k(x1Thk(% z^sAJdWh7IRo}A$;5Xsc4U(+bvVWgipcUdx)wJsjnUc5`63dQ9K#pPS$QEu-heOS!k zGz=4{cEI!ag8*Z_$j)`P#^J1`mM{DX%J^=Ro}`VVP=~wpWUVKKdfkNu2yE|)yL1n& zs}J9$FR=FN?qEGi^^ItFxB+VOXre zvSZ-l36xkU62&5`r5@)bNX((cmhZrJ$`*Y;hWw~Ds+smW2IIVezMgcVBSik?1=S``oU>LN_#Xf$%;AYIt#1KHy!gJ$Fe@&3 z0e#9qh2<2uB16A{i>55t3J$kljw>XXXW+s_w{e(oIaF{M+z#r2U903>-w}iNXK2?n z#Zku1zaxjNj<0MtWT)Z({R@~$*6=|vSNQx1H>o_IpAu=RwGk&bEbn-SrkoEYl!R^iRSP?byiU(PPXe7chC^zP(e65a>YKLA z389W8bRi#p$VfqzBE^=hx;Rcc9Q+9+ss=-8XzB;H>4WlRUP8(<{CWdt>3gQr?lY&8 z1?6x9Do0*hi<~sdJd=?q4CaY*itQ-B-kq(zO3%9XjcQaot$xz@D@HJfKWzka`9m^@ z!K_Z~KJ|h^uy3WK(O@33^i5Kx12!gb)|1!^+v(zDdM;|1Hqhi`IfkD_5yV2-U%0D) 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Ê-2Z|K;gPb(~_ zkJ>`Ttrph1kJ?2oHdnLf`(QiOY9G4K>ON|uR{j-2g%mnSp+EYlO)ZB-*T%`L z_EjU{xT#-Xb$0iE7Z4AI=LI?C3aOyoLE9zjnG0Z(mMyNs5HlN_IoXZAYWHy!e?eDI zF?&(H_Dqs2qUnhg&l*cqhahNo&|hpbscnQyxtbu9O74~NPvJU{foWvddoQ@2+fRKu zAQa%+Rv=!|ITB}^6Z+i(W>7Hm1E#Zt(50xulmJ`RP= z6vl7KH~J2M!qIy$eyrJ}YUtzjbo3N@pMl z%iOKuZRaixpz4oRtxetq*R%l?^RkoQ9DR@Nja3Jr>F&p>0m(fGEZlzRn_;^rU$&&K z8x}P|tLS_7hJ2b15QFy<0t|qL*FhU@J^^GW#;MIa2hg~oQwQIDng&TR1$T&`=)muY zARQO*6dwl3hjG{lzkFO_=i^jcOa`@TUMMJHL2-!RRIT>AWqn6N(djlPoOalf&qp=h z9u)&y3BXunJUaHz?$vC3yxKb0F`4M#ewBRI7wzZJh)r$SJ+?7kUC^cKX&8l+&rLRn z!F>v1vNOY}HuEU0ftzII8%NQo7@!76;)qh3REN`N@&Cj*o2;@`1Jq^x{+T3l^2gW* zRTuYG{`tszwWWUcJc_QDSx_?ODs&eNvuV3EHY`Ey7V*td+J@FF>PT7aa zdAy}D=8ibHv%^}_X_7NHj{8~yK)x-|=!nA3ey9bq=8;~F z)h*D{cZ&2HG>E*bQLjBix9REBJI7cSH9q)tVWUzDr~l46Y}uh` z9*Q8V*{=kvzIZGr=z!8H(nTbizlZnAB+i>gP~)rhARf&oanAvzyL+R?EdPKKG=3cE zz;R)Se#u}BZy&{3UcIUY2VR}yA!F#C8o7q!U%yOtk;Sx8-YtUgvwRLCvzWH~ z{OBiGoy$e;8n1!}3>I)u8KG@Ci`QYEKd3a*+I@za%s!~J)$SJ{l={8eY;ZLH8DXHo z`b(fj^RpCzZGLH}T|DiC8i?lmWU3N9m5qNaQ_1}L99CGdW|qWxEK1{rDaA*q;mU7dVj6J8QV^OO!VL{;gPWs97(bHZGlMDslaE0>qasA$_L zLY7w;&`@Nqo(jGwW!A6CRMN!;DSoewL$ zEn}!7-G=o90&L!4Wp2h~*|q5Re{`+iN4UCHE)G0o9*C}$C4%S?R4&ngJny4=ZFBqy zSKH93lB8pm2gH$bq1LUibe3MV83NFE2TDZ^Azte1ivp&J3e=D3RD@Sf347uiK1nD8 zU}uaM`-}(uqIu1BNuYE;k(f(4_|WE9R(T(w3$K~_dzQM>#|Vhi*zK>CfL{L$ca<=I zuB(K3B8WafcA#X9TEpM@uwDtzkEKn-5hc3C)%~>4l*r6hBVO#yMdcr2D~~8$v|*I^ z&=I9&XxX-UG+H8B_F&Y~xwL6kZW)G@+EFDX{E3fID&&t>>8Jvuc``-N(ON%nQ=LA7 zD7N6J5%ip!w4e0jB@bu<- zA1<2z3G=_DWj?+vHNs0{-`rO%4QxF7BjkJKzS>fch~{Hl5icE6nq(~dhDLHZni9)u zvlofAi>OH&)@Jc_1p^y(dXe(yejn&{0!}oZ1aA|vPJ|Cu$>3RX(vJQn4_VQ?kRn8F z(0CMu*g;Vw_+;iHOkj5xVHSo<=fFwsU+KdFLQd)w!btj)6X;X4fA|8HMWv_YR;bhy z{fgd#A5}%L6EH2N@zfO(T(Aqb*NB`0Ona(^N}|{L@MrI_@r^Cvon*>R)QySA)#D%veGa-Y)tScAh#C;AYHvWVX6?G8Lq6_IyU5b-yM0Hc6J@ptSbedM`=9m2^loxs~ z0*k`pR15|m&_HeaKu>_I{6z9Q@MK4UdO2@9hMd#CQBqrvBx^b$V{?t=Ejg}Cd%%{E z{rHVC)*@C&qJ#j8{Z{GM#(2}MU4wH*ZRiGXu>Uc z-u6J465=J`h^YGPUhW5LZ|V@= z*12r=tU-YuY+hw7BSo8rU+c2)YLd>0Up4qwAN&Cu=V`FmOSSo5<=H+6M^ z!X`fB5#)~F=gukt>g&tzv8=O7$Ht_Kkr&p{J_pbi(cdY(Y|qdfQ%*}$v!LN=TcDNKdhR5cc8Xgo1^nZce!lkJs$XcS^9<3JG`J{Z8qpB({YAb6t?k z&m|N0@<~Ax;sy6lJ}bFDpDEf57YOiRC^m=UK|m{i3o1AiU*^1w@jVXWYs;=@*V zQG?jtb4p;wgKp&7rO!BkM#pAODXc)Ts?t}AZyE$Z~!CyoSzm32is$M$Pm<9I`5HSENw^OB44X@J`!l{O(*+!T4H6qvYN+_GSszE>(x^z-cf2cncl|iN) zC@k|NNmUN-{0W)}a^iOQWtX1S+IM^zreLTMNYHEz;74UV*`4W+K{+9-hhhp+W`(K& zBKXK@T2Rvj7|aPNy^xut^60-Sbk#)EJk3L`dP8GNr)iySLv|o1OdJ>mm>8=_3S4DR z#Z4j_7878PUOY{EV@xg%G$0NGa(X`qHrwxD?Jy#4P{dPEGa3;bx^X_S9!eQSJYNr` zoFY06z-wgFHUEsAuwN3bUC09^7R(r^56uf8(XXaQlFi>3orTh76kmoD2=XMSx_Ug6 zL52LUucbnyK8fpkWFi~`>q&3uBu*f{1JNiq0Odqu8LUvKG!L*bGv3LT&d~e}*!}vXDvZ>E$&$<^LWM`k#Rr?n-lC8~in+$H6o4GJl`%eu9@{Wa3+8GL5Ul^eW8ZTO#ux~(p=c0^@Yg}l=nQK|?Dz0S_Z+*o2+~yo z3Q!sskM%!nHIWd7Pd@$eR6eex-|lq+eW{s;qIhX z;qzEsE*Lm<9~g;sAG~e=pvz-a{k{pg{I?tuAFGB{U3iOKwS}Isir*q!%fRkrkxd*B z#qB`elZ`y=85z;r1tNbwxGR`!<9|ar(>t+dZbR&Ci(*5C`m|{G#e;0eVb5@PJhOJ; zuxE(+>z@^@>ab^0q%0k-o#|C7zvBW7DVY8|h^Dvtb6o7IFzf$2>jG9rZvj133P0 zkxs0kx`{>g0zg6Pq?G0omJA_?CV7FFtz?(9AMt!uJp&^*>ru~0^=&=i%XS|1Z0bpD z;kt_XB{sW_drNlrsAn_Fpx3dl6Gj4>TW;Kqz=WlT%&0v6)dpcYAfJ-Z#R`x+VxwEd zJ5Q&*U{#S@74|0SOI~VZo#sBR4ui0d(!J?txk7-ys;`j#Ir)}cDC{zo=3&bL$R_3R5zwyj$VXkIa-W{! z{TF0Yn8puhVPOw>P3hqqjDI7iP3>;c)W=DE;26?SDwq$m+2qRZUb=5Fo*EcH{RNry5%C6h!d+{Z!w2k>GL z_Dp5miswLa#&+QS1`<8;S&!di{nsfeT8=j=GH0DKx5Ed}MNud4DaEa2!kM~UaqB4L z=Vg2^bio*=c%b|+bbx0nOu=k$p5m*eSb+X?9)`?!u;5~=@-So?z~GC0l&AF3)>G(C z9-9633bgL7^=PPG?3eWzsb0I9Fr~4=Mr=^JbPsqPJF)a7f?v$vuoTFK98El8O$yR& zoc)1WBFa8rBf*47@x>o(&juxE;s%14AuYs1P1&#(Nvwe`VsQ_LV?K^o{G-=si>G6J zvP5w^TVi}gQ4TsF4}fJ{6N2}0yiMmrrhfp!45WP}i+)4t(d@f&nZK^;5ghzp&X&BP z^lg?=2P-tFZxk>;#@|rdPVDSTka7f#A&>_nE;}lFK1nfzG_>aSNvwFfSQw9Pd70~U zygti6BM|sG3U42xBl|2bm2>;&;g#&UH zusG>1vG2!PZB*Je`|EcDS@ajdVw1aU+(sb3Uk4)HrtcN=5LWw4F`2IJxag)-e$1E2N z7ocPgD-owx9(lb`;EAKuie(4;E~palsOyXd%zd*mI-^bh|Du7v_GN9Yu7Qt!rA`B% z^s=sjw=JyGz|Wy2+|a-m!HB@6fqzr0Yn?el13#wi|JA^=-ThTd+=BnEdOwT!I@LS8 z{0Y_jMzB<0_1mNw=84oSq)s8|KE#r2=R4A`Wh}lC}%r3t6!v`S?+G)i8Y!_EN;28 zt_H9%Z$o5WyM;7cvv1oMisp(*QggG0Y)Y{=%hdXX?&;mPQ9t^V~K!&Of zs$^+|-HXx+LE_+0^iwC_lYzl6jF3BjQ^CsLRyuhPeV1%PM&%EO`9%fmn6Gs3Nzg?^ z^!yS{>uhp9b`*DaR@JgC`HH_fxpgJ`Dqm^S`7}zWPapj0|3LB!?!H3QGVU%Wj+*S0 zbM{f^sEcy-8BB`aTa<9^!c>HMZo$q62DuXu!%Xi$X!5C{7SZKfs)*5V?IcP3jvHzdpD#-Ix8snv=?SH{1qKe!Z5(uK~Jkc`|Dlh zl)vuT!-}C^!>?+>S3;2zbC|90w$`8^0MQPom)of#s zWzkG6wYv5;gq}!H$_Fh16A^-mKY4R}P#?$=b+8cpI#T}Rp@=QT@epROF!$9pW;Hwn z)lHqnA|GwhMJCe5A`Xgo5Grz;f0&dZer#i~#pZj6QB~J_3%2q}C@Z2Gv|tttGxaFG z;BX+QIq7I%wfQ`a;5kjCQozi6!{Y6!T|Q0OuLsn>&x>Z<3J+_Q1M z%&&!|ubS*p!$!5R^cfONO~fl_(qV+tNKMvbei1P@=%Gj5CsDcmQM@WsEj0;q+R)&7 z3nf}s5(~8g5Z5eXS6f)ZJL5z`3ec2`JTe;7S(SCMMh-=mExOTiF6tY-H@Ed3f`Y^<3h@3AaD%)&OB2Eit3=0;P%Bagwf(&5NKlk;z1fPhoK zO;Cs+jqHmVLega6XD9#*Cm7wO@z(nxAl+!{7`RRc`*OPujuqf*`zn~S$<)!X3KlOw zk*G_GuP#CSCEhf~*6tL-4sll#)QO8UM;>NfTuHrYbT8E$)yFI(9?pA@(CY*4OV}ryOg6UfEt9u9O13c9 z5*kDs>}LfPQPZe@8>kHDM~7FlhHsm?Qo??3n|i5}_f)V~-Zpjbyz*J%G)CuC6a@kx zTC{|xm64D;p#+%(*9AjAy=`jdKS&_;kx29l;L5|yl5ZN=vcoH)o0)kl3dnBahcE`^ zQ2sZYoo{LyT=lX@WaW1$Ky2Sa04Z7`MSi}?Uv0a)f_<{wvmLvhZyMvCdz$s$VhWFK ze-b@h_f4)FQIg8?93pjd#pbg9e?DT=zusg@sT2{ity@g(Bk~2fP;cQ(`UU^C-c778 zbi=(&>MR%?h~=Y?LVC8v6xLcE)m$z^jPD~l4O-b_-!X-m22PHo9c$&rc5dBrtsT&kfa2bN^>N}_eviYNbW(9x?v!Ab~w zx{pRfC;U}2Td?2CE}I|h20KCW?H#H> z@)62&bA{xCW<@-Li-;F+!5f`RH{EfYBW|l4_=ucLIq4>b6N@R%46vqiW+w%M1jHH! z2{DZ#qO(E5CC*|#A1NvB>khLgKT_HczqwWjvILV+{H4d~SS0wJp$GXnDhnhu;*Ojz zDTg6$L%#eKfeGg5MQHu_#z#YC!Y+NJ3|7;hQdql>l@pK7m{W!0s$dnyA7mSXU7K!4 z6U8S?qQk1w$MFp)(t)^NIw~BJ-yZnY?Qn1$h~g}jHd5PRc5!!86`QqPX|Ban)T-@D z=hmM-rEhdyMm15q&t&Kb=&TaO(`v!ePurD{faj5po7dnwkFw0Vx~si4v~1dQxFCKLKmL9b2&XK2fGd zDlk`1r32fT2{_rLRCSDlh5(-*QdN$sDvFOlX+?1_9IZRN`v#hkTpZo85CV^m?&bVH z&p}hZ=u;(4@e;-rT-l0WcBw!K3GzE4Gnz5}fsFQdzsH(o)zkn`5N+Eo=y`*pqC02cSiXM+9#vM-E;0^Gdmh zx1@D__;LPwBUof7W4e>cLuPbGi97@0ZIOpt_=KotLk614t8`Ovy$4913W{dCU*tKmsuAr*4og_ zRQ%A2ZcP6X`V#2+CX;9#&XUh*q3**g*sOC}U`Crj+Cf5iUB=VlN&!iX+i&Rk&~*@C z7yvX5NY#rNoUHmj5W`m;7b?U{n@3U)1V+kisq)n&xpkzX0jN)Ef`zw!Ac_w#Xwm%w zqV@B_|3T{n0QG5E_^tNYEp7F```y{Q4)U{BT}fsv>dcj;yOvMnBr5&Hx|81i(7Rx6@B#>t?hzP z0l~}V$JUoC*^CRC&AOz1?2CxqbU_RCjewPgP`a%Ixx|$|ooH`y?t<1jquB$wKc1L{ zT##F)xFAryfLI_koA{#z3gK#pdogEHWaH06f&V zncIizz2!@Yrfz>MXYVjUl?p*#!Ek+0mY|Wr##k-u|6PLR^-8cjgNpDyO_fx7D?Rs0 zG!c1#B2(&%+EH7GUtcO2-akV!ybdV^!N3+QDFxP2QVKjvNx?DoupN|o z2u8suGHvH?53GjqNQnz-|D)E<6IAIdG@3>I zsQC>X^bCy(VZL(`qenN75yuUvK)1~sUZ0Y27%`?U!(IrhuKh3=(<)KAM6m6h{C_kXH?X=eryzRTkY*M+q{};VkKj z)~;2uXq?1+Lvbax_$XQ?M?#)4x02;t(frhxeyw2Ju4pZ$UU>7r6vj;yW}gAdoP}?2 zMi=DL_@aZQVN9iZrNTfb7}$&)6zXRm3YU6aHpBWr&W;F{eWkGi!#ZLtnKW3k#V zSggJ9|J*5yweR;9tF_o-y>!9C4qnr`_uViF1w{U~4`>_+vkyAZVH{M=>rELN&d0={ zH$G@upqs>9e$xEg-S`Nd6C@Ub1g27+fLZC%c#bHD*Eke|g{gcAt6-mGAb zpS127p+Ab9frcL-Nf#eea40C9jin-cR5@+3yWha~Q`mCIO+Z`v#^x01A)(^jkG|tX zdNE&k(03P+zQhhGcjYhmrjtW<3U9}OEZpQzs_`eAMPvQT5 z{NI89zg@Dh7fZBW5E6Y-qD8iyRU#N3gCD%#oGmn2c3q$KCDd&=gZs>`Tz_4AM6G0Ownza|rL(_ry+t(n!l!HBi;vI&TN_qrBl z{n@K-0sfL=f4r`>NKX`+kBpbyJ!?4bW~(P6OV*Xzh9?&=lu}NPc=Ituk13T((vETlZfklbk1y z(jaRa@=WDTI#sjhe$m1jy#W>-`UT#ZGqH~TqP0+mB5>#zEueF^I*PIj>lA|?ph%NN zDt{@mnwf8Cwq6=2qQ%OQ4<9q0<^d5rg*92{jy5gh#648BBTZOzf06Hau98iqH1Wbq(CjVU9GpXyDRHl#%zWYRCvPzJQD z@d>@F*Wc8_)LSzuS^JyX6B%)W&l2Kua~^78R1WL`@pN&L=nHo)>V4tWQ6gC(CHvtC zy)WeOU@##1LJn^%g6Io5ya5GWB1u8{Ys{JY?h&gB5kV7J@q`@y;Wzbhl&b^Rx~j2P6n193dkc1DmllLeSv6cKMKhA{X~@@X;AX9d zWyse;2*oensjc0WwONaHSC?E;SpN=6p!r>b(MifW-8Vy|!{f2T$V}UrULwBf3X_>T z5Leo2$)e!@@|4UYE5^`|mpK0B%M3Pt|Nw^)O((5^H{$C_Q>yc>FS76|oaY-Se9)-UD zi^9mu1`}P{wQiX%Nu0&bk&2yFkHppFirhcHPZ+$N;?N{BWtx*l8pOSfxKf)goT}jq z$%+Fl0(4c9SuQjF=TXYIEZtg!Ok@`}A>-rcdbW(F zICROaB27v9Nct9L7-s(IqZPTf^CR6;Pvp)!ALgELBKNKHwzP>S#DWO17M)AjFIi%dURrUxeiKI25L&kucx_|PBVRyX+{q%>l zj2XvOsQ0Syj0mQNGrhzLu^}3>-*JBENm|%y>}!@#|J8D^EKs74^h7V}i8lS8MBuxM zXX=TD>WN-@I1yPKeBKSYAr!Lq8{D7>m^;XziPDCJGB1_Ng<{l zXFrhk+u*FHi-P`Iu2~K>CvUm>UpSxn$~#s2rwde{Uwq}AVamD)y=tMA&3>gpKqyYy z1D~KqssgpfbnqTM%=k>x=-dA^dugBdVpGutLQ7>!-)-dImIS+*V<{mnRT5r4Uq96N zB)ok7*eMpX-#cc+X5qRH=LhMl)X*F2Mqhl<+y4uWb#0E`jzw`APCR7Fr*Gic*ZaMD zy3e8R7?OK3!K!u-5nWRA2r>Lt?2c&IPCJGBUF?^LhOv#7m05(s2JUz|p&phV@NO2E z_$*dWtmE;PR1_aBX=semaVa-{Y)kf&zY@Sc7^gOF*cy^$T4c*lu*03TaPRNF!A^#x z$R`kBy?rf#%o?Tj#io8}l=f=xh)8I=g*5{8Ay)91^0Gc@~Va>?^8l1cwJG%V4npx4R|(b$PDS~qu;@_rYsds`byEk=~Q z2d5i^pc=Gq#Mt6l`zmX5w@nFEyEoVGKeCm$4e9RDoE}|i&c<}}^7s0;94ju~3R0}! zu$Q}ewf3Iag2eRL*$O9rA9nL<)7xs$`vYlZjzR;479$l5?f~){IH18kMJ%`tY4Cj; zgZcIeHu0F^+rP@5tnqP4mAFq>v#1A}ikg#@k;lXSu3fhK9Lnf6hYUv`HW$Kau6GfImn@g_of_%iRYraIqk`2esU1yP_?K7sfDQD!(t(ZDCH0( zxNo8Tj*k1sBNgmgv{#qrn}ezZ&SL6XQTRY1sO&gmx9{#{bB~H;NYF})ohWMWgcr&Vs6rkWr_Y@o_l*U%~ocHxG@krp_B)4nYfblOYtyN zO1l({4arLTqc+|YpQ-jyEA7d_yqy7}l)=18H^P_*KCtgc3LBq-s0=jNN)MklkiXzH)8MCbn$Gnc9lBYE{*^@;rYy&uV%ldcA-&%cbX#MlnI|T1 zQJ1n&w5;1=f)caC*=EK;s_vC0_Qo&fxz@La3Okw_D%DzqS;!4@nmfdkvu~LF>378q zbMw#y-SjTuRdmBDekX{o`$A69sy&^)>1<_cQ^AhkF!xE~FNzi;J+n}Y(mget9vhZWy6KkK^{m)W4SL#jRc&SUN1N|tg9#O4x2Y^V~8aZ0D-J{{2$Px4pv zlp~SSe#`uUm8wvPx)RHU1d_%7YF?pg?%wRoujWx53lwiF{l6gp%Y{+=7&D)kCU9wM z7&muNvw*pfnm^6BG~iwQ$H&b(b<}8I;YPRy##$HCzT&)fYW+wk1oEwfT5q(i_y$D_+&pf5+BezMT%XG0qDXJN3STU~k7;`3>LdR@s0IX9u)q75e!ynsbYD|Ie_8z4 zgnDdAD=HV8nG>;5sSlsw`etqW!P)g8e?yMLr-qQGO$fD~K#*ENYDFxQ(3~U8%MQn4 zKo{_TpVIXwba$AvZ$KA9W)Csf4V@(m3T`yxT!TGXtFieux#G0DMgu^gENTD((3K`h^+A1nP5-mFv z%kKrzSQ1`kieje_~C68S@WOeW&VmR*WB`ojwJ9Y4Hce zkAS1<-axF+myWSu4=5!Ru3$R` zn1bASw_V>1Ff~@(<6dT06HGAyyD+ODy=a)Rne5V1dx(Ci zUD_PsYI3#!37Y-qY2B6}Q*T<*=9ObY{4~+jyUC=9vNP){^oryvR+DJz8W`e=7bEFy zkf2V1)pXE6(*k$6*FQYaG_7Nf9}PA)aKtka#M{Z#S;-{}1?sBv;^!4?^dQrUkS8YS z^{uP@uFjx>MT>%2VROf72bto!tgIg^q{Gr6&*!y~nEpvCDPqMDlXrqi-RvY&U@r`3 zc(eyC3_&Y?(Dkg;sQis5MV=Uq{RL$#y;Kt?gi%&dIciO-zR+?;T-gJ;FUew)jWY2y|Fhw+!_K00TVCZ1e zBh+tRA8Z=vbJt3>h(ERb2L7@ugH7F(Zp=Ew)UK)DI9DbpOd9tXi{ru&Q#9ZifM>jK zWR|x?6m~t7BVsb5Pt-&{VL9@01n}9h5*Vvj6UFkK4XN1k^B)>tn6MX#c6me!N(Eq>j${ppLG*9v9^1z{OG`v~lHFEz2Mr=a{ph zVZ;!i-xtv*R(`C7G=1SQS$hyzzK_CVj$&!yTEP1Ccx5mDj@Bkv&KRWTb}cuF$Mp~P z(;dyf!D9`8H-KG#(i|9rdB{1L_!TQRneqyqV&D46P+-?lI!PZGH7TW<|FgTXDJhK> zT6N&lIGifFCN5PU(bLiF^<*MGa0gSoGlvZS97BaFK&{ir=ikF?8YPzVb>jB=9~box zb_&_~r+WYq+i~%plCB@;zN0JHKc!k1_klGmt-!M@n^>lqw9LesS~k5*8xz#Cv+LTZ zO0+`tcUd%Z={KzZ)9`TYEYsQzGVHNoc(*kWFsx+ z?^^SoMy~7X^Ru%4M~)+L9r9j@yip zm^bcf(HZZi=nD!=u*|%*nU=PcO}ss1K_qP4#D7x6f^Lp*tuyK&A!6LbkLaM^@Wi~~ z4^=%38-GIEro+wxW;js=j1DB%Fw@!xSVj1SQP>=lH08}WdfviD&A%E!>|h};@o}nb zTDjIHx+R{)6oYK}DY5@O7N-d>C`Q+QaF$!!Q+K%h+)Or&TdaGcM?W{Qrs++#FFk=FpqcYbf? z|EJcwZ!Z{SA)$WoiZni;)Z#Gy)FIdd>({TK1fis2UvJ|%Frr)irBQgs?U_2*jT0Q#ie1lvp=hJEm>>=Qu#tXfj%=}mQO_wF?x6EK-g({poZcqwIu6{VRYb>qhcy~i)euy;qArN? zf1jDpE{pYie}7*u+0We0%$%7ybLPyMb0l1lG$_xVrUIx*z&z6K21~n%nl`(}u1DUVS3v9C47aR6^u=bs@RY#K`!`^nN0px~ipDy^Q^{>S_F;H(Id8 zQ!n87BcE9DGfxqvc0P(Hs=i%nC>)hJt79lVxr1u-|+Q+9p4nk2(x5Z;b(UZY(!t6d8SQvc0MHvBu}5R?fM&B9O1{QkIW zldU}%GmFE%jN`XE)Bs=OKfeb_X`2u|cnERL3=wp(qB5AG>QItxe z2N8gsIcnyYlvE4xt`kD)C2D zvbRtlJD^Jh5R#(_Cj{lyZ)iqO8%hchRzrjM*>XVhD_!zNPpQse!WAR#j zkg^CLY^}bVl7^476w?kLzau8%k1(o{2)|T+M0GXVXnYlRgY~oOyCyzivq6kmT8MT3 z0%aEpq;fD%Q{)l>LyYhSwsoyO9CW-CxXxI`hXKZMtG=@mjE|dEeHUflv@>i|gT9x} zW@Fzp=>Mhv-0kX2(sWm&gjU1UGj}2V>DpfZ)XQ~EMdK6WIwp>}i|T7hidu_lfSW0p zB(PVl#wVoCNyvtQ!*%Y;B{fC0iJ{Ro{CpOb;8cQ(5*ZwKrqzRyy@~qcpIUb^0!W$rYlwRmdb@%4-I^4}xw)Yur^Wdm|-pt6)8#&Y_c%s8SJ@k|-ms{`A@ zSpS`L*8`;r$Ah@)FUYCMrS~NKotMz52i|O_9L{qun)hZ#pLi+*Hf|}HFS=nLB4D76 zh+BiW>P~c{yP@um5aBuX8O;4|Hx*q&`B_@|yWoYW{92dtsGMz3z3i+`+UD^9w7kGyd=cs?91XFZ$W_09Qt;|%d%ubgwPu#T?+1}|AS)t|e)$-K3VS&{N zu?}K#$7=AWYVa4iz<=V7E${C`8}t$6GGb`|0a-D*n>B#G8o+}t0K=)l_fD}P4RW=T zc~Y@ZT^pFxF1JzxSk@>p;PzGXoIQg$2lnSdL$qklp>`cbIu!e~{OKAQwnG4Z zWM*t)u2vvgE3n$7Kqr?1*KU!-{Jq%7omzo&x2Z*Sxu-=~s+wKCZ7X^qX<$*&j=>!? znZCsvk1@r(7&GItpM%7-`63O`^J=h*2Ngc=lS zc#9pN`6kIZ1U$$Si-qu?#;uTl5UKMxgzF**16brZT?nouS{M{vtJ&n){M>E!bCx-? zt#+u*CmjgMp9n@G-yt>!Lz9zjjx%?;O3Ws1H@*1VX51yN$u_sub;sl5Y;y?xp2{}& zilFZ8r_&gWJ1KO(ARnKT%IhTLi5_L{O?fAdG6&P|qEY4^k>M8vPNonZ&sD?vQ89hu za(yjz>NWP|D05=oR~y9DdB|9%zMJCHd)D{#X(*f2C z7!;m210aUMYQre9+JFpR{1PyhNuA0fS0Xf$B0Ifc;F-uyWhs!wo*8Xkubl6CotbjX ziM`+ID&`8t^+0b7ygam3q)Cwh%~NbU(iM(;gRSLQnhJqz4$zeXrT83kIQ{O-F^AFb zDf}uoyIf~g_(=M;i>N@mp%xfDcSRr+3a@;>OwHX&oB7Vwbu8r}bF9U~L=O{$KOSBX z1wF^d9x}JfOIs%p_sSB1IJ&>*6X!V0C}1@8d?~yY|d5$&r>F_GOSJPx&xMOuL z3bX{1-}<~zDf`^r?Q1q;ta(s?8<7oKAQq0RK1|_8-tX&M?C4l?@7TFN08}|YSx5A$ z0vAUMz^w9+=ZIX@&~v{J3hPPyfVB;mwUPv}U*;&0iaqNlGfXhIRX&Ql z%@XG-A)8Wyl>y3V#1zg|I`v!dyG>kKkS;#0I=bS~o=3hC(g>eW6jAM?MLkbZ z9OHDa0&0_J3_l_W! zg2PRQS6QW!$!9`K8<(TRagn~EW8Gpso~zK>>c>wwKI8cT$7f$&0{?Fx>@fxgL$F~- zUHm`L?juQn7k?37aP&lGO#N7!aHY5M+I{XUGhB(t?7tEV0GWV~cs_4F7N7K@1BFpI z7WhV3k)7|cvK*>p(q=&;uRew%Fi0wWv~&~d;}W0ej@7fn;c%h$A+oT$XZ2xh{5+2c zKZj0fJU^8oPYTd-412`fAt6j_EY)5(k4OqCw)0YGr1c?N3w~IQ6!F1%Hn~C{9A+lh zB;x!@�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≦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%0D~ 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%0DJid7LKd=w#+s#{40*$@df*^~UpeaYgeMb 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 P0)4Zgi;F_w&TMO< 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
>n=yu#kqUWJA@;reKT_wWZUxmv7p9;DH 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 z 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
  • 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

    `e=+qE*Tmohf?m=|c0Q=^290n|GmJd9S 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


    }Y|M9P>F+=-Lge1q)Dn$c`>NSG_c?Oc)HJ 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

    p(>N2l4!j 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 JtA%lW{y%^1$p-)c From bea1601994da22504c669f099cdc17367c45ac77 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 4 May 2015 15:23:56 -0400 Subject: [PATCH 166/225] jme3-android-examples: add project for android examples --- build.gradle | 15 ++++++- common-android-app.gradle | 13 ++++++ jme3-android-examples/build.gradle | 40 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 20 ++++++++++ .../jme3test/android/TestChooserAndroid.java | 12 ++++++ .../src/main/res/values/strings.xml | 6 +++ local.properties | 1 + settings.gradle | 1 + 8 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 common-android-app.gradle create mode 100644 jme3-android-examples/build.gradle create mode 100644 jme3-android-examples/src/main/AndroidManifest.xml create mode 100644 jme3-android-examples/src/main/java/jme3test/android/TestChooserAndroid.java create mode 100644 jme3-android-examples/src/main/res/values/strings.xml create mode 100644 local.properties diff --git a/build.gradle b/build.gradle index 62d4ac841..d9f5cd2ea 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,23 @@ import org.gradle.api.artifacts.* +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + } +} + apply plugin: 'base' // This is applied to all sub projects subprojects { - apply from: rootProject.file('common.gradle') + if(!project.name.equals('jme3-android-examples')) { + apply from: rootProject.file('common.gradle') + } else { + apply from: rootProject.file('common-android-app.gradle') + } } task run(dependsOn: ':jme3-examples:run') { diff --git a/common-android-app.gradle b/common-android-app.gradle new file mode 100644 index 000000000..0ec48fbd7 --- /dev/null +++ b/common-android-app.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' + +group = 'com.jme3' +version = jmeVersion + '-' + jmeVersionTag + +sourceCompatibility = '1.6' + +repositories { + mavenCentral() + maven { + url "http://nifty-gui.sourceforge.net/nifty-maven-repo" + } +} \ No newline at end of file diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle new file mode 100644 index 000000000..68b4539a0 --- /dev/null +++ b/jme3-android-examples/build.gradle @@ -0,0 +1,40 @@ +dependencies { + compile project(':jme3-core') + compile project(':jme3-android') + compile project(':jme3-effects') + compile project(':jme3-bullet') + compile project(':jme3-bullet-native') + compile project(':jme3-networking') + compile project(':jme3-niftygui') + compile project(':jme3-plugins') + compile project(':jme3-terrain') + compile project(':jme3-testdata') +} + +android { + compileSdkVersion 10 + buildToolsVersion "22.0.1" + + lintOptions { + // Fix nifty gui referencing "java.awt" package. + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "com.jme3.android" + minSdkVersion 10 // Android 2.3 GINGERBREAD + targetSdkVersion 22 // Android 5.1 LOLLIPOP + versionCode 1 + versionName "1.0" // TODO: from settings.gradle + } + + buildTypes { + release { + minifyEnabled false + } + debug { + applicationIdSuffix ".debug" + debuggable true + } + } +} \ No newline at end of file diff --git a/jme3-android-examples/src/main/AndroidManifest.xml b/jme3-android-examples/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ee80b6b28 --- /dev/null +++ b/jme3-android-examples/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jme3-android-examples/src/main/java/jme3test/android/TestChooserAndroid.java b/jme3-android-examples/src/main/java/jme3test/android/TestChooserAndroid.java new file mode 100644 index 000000000..e704bc85a --- /dev/null +++ b/jme3-android-examples/src/main/java/jme3test/android/TestChooserAndroid.java @@ -0,0 +1,12 @@ +package jme3test.android; + +import android.content.pm.ActivityInfo; +import android.app.*; +import android.os.Bundle; + +public class TestChooserAndroid extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} \ No newline at end of file diff --git a/jme3-android-examples/src/main/res/values/strings.xml b/jme3-android-examples/src/main/res/values/strings.xml new file mode 100644 index 000000000..b184fa936 --- /dev/null +++ b/jme3-android-examples/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + JMEAndroidTest + About + Quit + \ No newline at end of file diff --git a/local.properties b/local.properties new file mode 100644 index 000000000..27e823fff --- /dev/null +++ b/local.properties @@ -0,0 +1 @@ +sdk.dir=C:\\AndroidSDK \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 346532460..80d932d64 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,7 @@ include 'jme3-testdata' // Example projects include 'jme3-examples' +include 'jme3-android-examples' if(buildSdkProject == "true"){ include 'sdk' From 58aed8be237f52fcef502e02d0cdbc3281a6027b Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 4 May 2015 21:49:31 -0400 Subject: [PATCH 167/225] Gradle: dont build android examples by default --- gradle.properties | 1 + settings.gradle | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ccc3ed460..a00139bdf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,7 @@ buildJavaDoc = true # specify if SDK and Native libraries get built buildSdkProject = true buildNativeProjects = false +buildAndroidExamples = false # Path to android NDK for building native libraries #ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7 diff --git a/settings.gradle b/settings.gradle index 80d932d64..89b8fc075 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,7 +34,10 @@ include 'jme3-testdata' // Example projects include 'jme3-examples' -include 'jme3-android-examples' + +if(buildAndroidExamples == "true"){ + include 'jme3-android-examples' +} if(buildSdkProject == "true"){ include 'sdk' From fe057130a4d432f0e153fac3b7d28e42c07dda69 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Tue, 5 May 2015 16:00:43 -0400 Subject: [PATCH 168/225] jme3-android-examples: fix incorrect dependency .gitignore: add jme3-android-examples/build folder --- .gitignore | 1 + jme3-android-examples/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 09d505187..6b6f30e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /jme3-desktop/build/ /jme3-android-native/build/ /jme3-android/build/ +/jme3-android-examples/build/ /jme3-blender/build/ /jme3-effects/build/ /jme3-bullet/build/ diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle index 68b4539a0..f1ee38739 100644 --- a/jme3-android-examples/build.gradle +++ b/jme3-android-examples/build.gradle @@ -3,7 +3,7 @@ dependencies { compile project(':jme3-android') compile project(':jme3-effects') compile project(':jme3-bullet') - compile project(':jme3-bullet-native') + compile project(':jme3-bullet-native-android') compile project(':jme3-networking') compile project(':jme3-niftygui') compile project(':jme3-plugins') From 554cfb8fabf2a5215c63c657875ffb0ce5b7f7b5 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 7 May 2015 11:10:32 -0400 Subject: [PATCH 169/225] GLRenderer: remove GL_APPLE_limited_npot That extension doesn't indicate NPOT support, just limited support same as on the level OpenGL ES 2.0. --- jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 1 - 1 file changed, 1 deletion(-) 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 5e3eeca90..776016a2a 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 @@ -366,7 +366,6 @@ public class GLRenderer implements Renderer { if (hasExtension("GL_ARB_texture_non_power_of_two") || hasExtension("GL_OES_texture_npot") || - hasExtension("GL_APPLE_texture_2D_limited_npot") || caps.contains(Caps.OpenGL30)) { caps.add(Caps.NonPowerOfTwoTextures); } else { From 64ba4794e41e012682d423d0e2580bf9b7088fb5 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Thu, 7 May 2015 17:47:36 +0200 Subject: [PATCH 170/225] Bugfix: fixed a bug that occured for some users during logging out the name of unknown data block. --- .../plugins/blender/file/FileBlockHeader.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java index 666da7896..6a5222a9c 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java @@ -31,6 +31,7 @@ */ package com.jme3.scene.plugins.blender.file; +import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.scene.plugins.blender.BlenderContext; @@ -171,8 +172,21 @@ public class FileBlockHeader { BLOCK_IP00('I' << 24 | 'P' << 16), // ipo BLOCK_AC00('A' << 24 | 'C' << 16), // action BLOCK_IM00('I' << 24 | 'M' << 16), // image - BLOCK_TE00('T' << 24 | 'E' << 16), BLOCK_WM00('W' << 24 | 'M' << 16), BLOCK_SR00('S' << 24 | 'R' << 16), BLOCK_SN00('S' << 24 | 'N' << 16), BLOCK_BR00('B' << 24 | 'R' << 16), BLOCK_LS00('L' << 24 | 'S' << 16), BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), BLOCK_TEST('T' << 24 | 'E' << 16 - | 'S' << 8 | 'T'), BLOCK_UNKN(0); + BLOCK_TE00('T' << 24 | 'E' << 16), + BLOCK_WM00('W' << 24 | 'M' << 16), + BLOCK_SR00('S' << 24 | 'R' << 16), + BLOCK_SN00('S' << 24 | 'N' << 16), + BLOCK_BR00('B' << 24 | 'R' << 16), + BLOCK_LS00('L' << 24 | 'S' << 16), + BLOCK_GR00('G' << 24 | 'R' << 16), + BLOCK_AR00('A' << 24 | 'R' << 16), + BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), + BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), + BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), + BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), + BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), + BLOCK_TEST('T' << 24 | 'E' << 16 | 'S' << 8 | 'T'), + BLOCK_UNKN(0); private int code; @@ -187,7 +201,12 @@ public class FileBlockHeader { } } byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) }; - LOGGER.warning("Unknown block header: " + new String(codeBytes)); + for (int i = 0; i < codeBytes.length; ++i) { + if (codeBytes[i] == 0) { + codeBytes[i] = '0'; + } + } + LOGGER.log(Level.WARNING, "Unknown block header: {0}", new String(codeBytes)); return BLOCK_UNKN; } } From 3f3ef99b8693cfab8ebc2e74c766ddb504ca4a16 Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 8 May 2015 11:18:27 +0200 Subject: [PATCH 171/225] Fixed an issue where AA was not taken into account when using SSAO filter --- jme3-core/src/main/java/com/jme3/post/Filter.java | 5 +++-- .../src/main/java/com/jme3/post/FilterPostProcessor.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java index a6e2e3c01..a3c136e55 100644 --- a/jme3-core/src/main/java/com/jme3/post/Filter.java +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -233,11 +233,12 @@ public abstract class Filter implements Savable { * @param vp the viewport * @param w the width * @param h the height + * @param numSamples the number of samples for anti aliasing */ - protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h, int numSamples) { // cleanup(renderManager.getRenderer()); defaultPass = new Pass(); - defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat()); + defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat(), numSamples); initFilter(manager, renderManager, vp, w, h); } diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java index 2cd8b83f8..462f03314 100644 --- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -170,10 +170,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable { renderFrameBuffer.setDepthTexture(depthTexture); } computeDepth = true; - filter.init(assetManager, renderManager, vp, width, height); + filter.init(assetManager, renderManager, vp, width, height, numSamples); filter.setDepthTexture(depthTexture); } else { - filter.init(assetManager, renderManager, vp, width, height); + filter.init(assetManager, renderManager, vp, width, height, numSamples); } } From ec7432c90c17c0a9abf406a29cb1fe32ab3c6068 Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 8 May 2015 15:07:43 +0200 Subject: [PATCH 172/225] Added a graddle.properties to je jme-examples project with an override of buildJavaDoc to false, to not generate the javadoc when building from jme-examples Also added a parameter to enable assertion as executable classes are only in this project anyway. Removed the global assertion setting as it's only needed in jme-examples. --- build.gradle | 16 ++++++++-------- jme3-examples/build.gradle | 5 ++++- jme3-examples/gradle.properties | 5 +++++ 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 jme3-examples/gradle.properties diff --git a/build.gradle b/build.gradle index d9f5cd2ea..8e6790230 100644 --- a/build.gradle +++ b/build.gradle @@ -175,11 +175,11 @@ ext { // } //} -allprojects { - tasks.withType(JavaExec) { - enableAssertions = true // false by default - } - tasks.withType(Test) { - enableAssertions = true // true by default - } -} \ No newline at end of file +//allprojects { +// tasks.withType(JavaExec) { +// enableAssertions = true // false by default +// } +// tasks.withType(Test) { +// enableAssertions = true // true by default +// } +//} \ No newline at end of file diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 186a6bc2d..de1c3931c 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -6,7 +6,10 @@ if (!hasProperty('mainClass')) { task run(dependsOn: 'build', type:JavaExec) { main = mainClass - classpath = sourceSets.main.runtimeClasspath + classpath = sourceSets.main.runtimeClasspath + if( assertions == "true" ){ + enableAssertions = true; + } } dependencies { diff --git a/jme3-examples/gradle.properties b/jme3-examples/gradle.properties new file mode 100644 index 000000000..6f0993120 --- /dev/null +++ b/jme3-examples/gradle.properties @@ -0,0 +1,5 @@ +# When running this project we don't need javadoc to be built. +buildJavaDoc = false + +# We want assertions, because this is the test project. +assertions = true From 31fc1d9ac691f219691df19763a56388990ee130 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 8 May 2015 11:09:20 -0400 Subject: [PATCH 173/225] GLRenderer: fix srgb warning logging Only complain about missing sRGB support if the user wants to enable it, not disable it. --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 776016a2a..66f4d66d3 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 @@ -2670,7 +2670,7 @@ public class GLRenderer implements Renderer { public void setMainFrameBufferSrgb(boolean enableSrgb) { // Gamma correction - if (!caps.contains(Caps.Srgb)) { + if (!caps.contains(Caps.Srgb) && enableSrgb) { // Not supported, sorry. logger.warning("sRGB framebuffer is not supported " + "by video hardware, but was requested."); From f6b7c3819a6bb0e062d7a81dac020eb04b64ee10 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Sun, 10 May 2015 01:57:27 +0200 Subject: [PATCH 174/225] SDK SceneComposer : added abstract ShortcutTool class that extends SceneEditTool - added a shortcutManager that provide some usefull methodes for ShortcutTools. Also handle activation of shortcuts. - wip : implementation of the MoveShortcut (based on the same shortcut provided by the selectTool) using ShortcutTool and the ShortcutManager. - modified the SceneComposerToolController to work with the ShortcutManager & ShortcutTool --- .../SceneComposerToolController.java | 48 ++++- .../tools/shortcuts/MoveShortcut.java | 137 ++++++++++++ .../tools/shortcuts/ShortcutManager.java | 197 ++++++++++++++++++ .../tools/shortcuts/ShortcutTool.java | 29 +++ 4 files changed, 403 insertions(+), 8 deletions(-) create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutTool.java diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java index 50b6d5cfe..71fee925b 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java @@ -11,6 +11,7 @@ import com.jme3.gde.core.scene.controller.SceneToolController; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; import com.jme3.gde.scenecomposer.tools.PickManager; +import com.jme3.gde.scenecomposer.tools.shortcuts.ShortcutManager; import com.jme3.input.event.KeyInputEvent; import com.jme3.light.Light; import com.jme3.light.PointLight; @@ -31,6 +32,7 @@ import com.jme3.scene.control.Control; import com.jme3.scene.shape.Quad; import com.jme3.texture.Texture; import java.util.concurrent.Callable; +import org.openide.util.Lookup; /** * @@ -184,7 +186,11 @@ public class SceneComposerToolController extends SceneToolController { * @param camera */ public void doEditToolActivatedPrimary(Vector2f mouseLoc, boolean pressed, Camera camera) { - if (editTool != null) { + ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); + + if (scm.isActive()) { + scm.getActiveShortcut().actionPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); + } else if (editTool != null) { editTool.setCamera(camera); editTool.actionPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } @@ -198,36 +204,62 @@ public class SceneComposerToolController extends SceneToolController { * @param camera */ public void doEditToolActivatedSecondary(Vector2f mouseLoc, boolean pressed, Camera camera) { - if (editTool != null) { + ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); + + if (scm.isActive()) { + scm.getActiveShortcut().actionSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); + } else if (editTool != null) { editTool.setCamera(camera); editTool.actionSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } } public void doEditToolMoved(Vector2f mouseLoc, Camera camera) { - if (editTool != null) { + ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); + + if (scm.isActive()) { + scm.getActiveShortcut().mouseMoved(mouseLoc, rootNode, editorController.getCurrentDataObject(), selectedSpatial); + } else if (editTool != null) { editTool.setCamera(camera); editTool.mouseMoved(mouseLoc, rootNode, editorController.getCurrentDataObject(), selectedSpatial); } } public void doEditToolDraggedPrimary(Vector2f mouseLoc, boolean pressed, Camera camera) { - if (editTool != null) { + ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); + + if (scm.isActive()) { + scm.getActiveShortcut().draggedPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); + } else if (editTool != null) { editTool.setCamera(camera); editTool.draggedPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } } public void doEditToolDraggedSecondary(Vector2f mouseLoc, boolean pressed, Camera camera) { - if (editTool != null) { + ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); + + if (scm.isActive()) { + scm.getActiveShortcut().draggedSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); + } else if (editTool != null) { editTool.setCamera(camera); editTool.draggedSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } } - void doKeyPressed(KeyInputEvent kie) { - if (editTool != null) { - editTool.keyPressed(kie); + public void doKeyPressed(KeyInputEvent kie) { + ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); + + if (scm.isActive()) { + scm.doKeyPressed(kie); + } else { + if (scm.activateShortcut(kie)) { + scm.getActiveShortcut().activate(manager, toolsNode, onTopToolsNode, selected, this); + } else { + if (editTool != null) { + editTool.keyPressed(kie); + } + } } } diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java new file mode 100644 index 000000000..e59fec76e --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java @@ -0,0 +1,137 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.asset.AssetManager; +import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; +import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.scenecomposer.SceneComposerToolController; +import com.jme3.gde.scenecomposer.tools.PickManager; +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; + +/** + * + * @author dokthar + */ +public class MoveShortcut extends ShortcutTool { + + private Vector3f currentAxis; + private StringBuilder numberBuilder; + private Spatial spatial; + private Vector3f initalLocation; + private Vector3f finalLocation; + private PickManager pickManager; + + @Override + public boolean isActivableBy(KeyInputEvent kie) { + return kie.getKeyCode() == KeyInput.KEY_G; + } + + @Override + public void cancel() { + spatial.setLocalTranslation(initalLocation); + terminate(); + } + + private void apply() { + // TODO creat UNDO/REDO + terminate(); + } + + @Override + public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { + super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates. + hideMarker(); + numberBuilder = new StringBuilder(); + if (selectedSpatial == null) { + terminate(); + } else { + spatial = selectedSpatial; + initalLocation = spatial.getLocalTranslation(); + currentAxis = new Vector3f().set(Vector3f.UNIT_XYZ); + + pickManager = Lookup.getDefault().lookup(PickManager.class); + ///pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + } + } + + @Override + public void keyPressed(KeyInputEvent kie) { + if (kie.isPressed()) { + + /* + ShortcutTool otherShortcut = Lookup.getDefault().lookup(ShortcutManager.class).getActivableShortcut(kie); + if(otherShortcut != null){ + Lookup.getDefault().lookup(ShortcutManager.class).setShortCut(otherShortcut); + }*/ + Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie); + + boolean axisChanged = ShortcutManager.checkAxisKey(kie, currentAxis); + boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder); + boolean enterHit = ShortcutManager.checkEnterHit(kie); + boolean escHit = ShortcutManager.checkEscHit(kie); + + if (escHit) { + cancel(); + } else if (enterHit) { + apply(); + } else if (axisChanged || numberChanged) { + //update transformation + float number = ShortcutManager.getNumberkey(numberBuilder); + Vector3f translation = currentAxis.mult(number); + finalLocation = initalLocation.add(translation); + spatial.setLocalTranslation(finalLocation); + + } + + } + } + + @Override + public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (!pressed) { + apply(); + } + } + + @Override + public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + cancel(); + } + } + + @Override + public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) { + pickManager.updatePick(camera, screenCoord); + /* PickManager pickManager = Lookup.getDefault().lookup(PickManager.class); + if (toolController.isSnapToScene()) { + moveManager.setAlternativePickTarget(rootNode.getLookup().lookup(Node.class)); + } + // free form translation + moveManager.move(camera, screenCoord, axis, toolController.isSnapToGrid());*/ + } + + @Override + public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); + } + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java new file mode 100644 index 000000000..38548087d --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java @@ -0,0 +1,197 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.gde.scenecomposer.SceneEditTool; +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Vector3f; +import java.util.ArrayList; +import org.openide.util.Lookup; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author dokthar + */ +@ServiceProvider(service = ShortcutManager.class) +public class ShortcutManager { + + private ShortcutTool currentShortcut; + private ArrayList shortcutList; + + public ShortcutManager() { + shortcutList = new ArrayList(); + shortcutList.add(new MoveShortcut()); + } + + /* + Methodes + */ + public void terminate() { + currentShortcut = null; + } + + public boolean isActive() { + return currentShortcut != null; + } + + public void setShortCut(ShortcutTool shortcut) { + if (isActive()) { + currentShortcut.cancel(); + } + currentShortcut = shortcut; + } + + public ShortcutTool getActivableShortcut(KeyInputEvent kie) { + for (ShortcutTool s : shortcutList) { + if (s != currentShortcut) { + if (s.isActivableBy(kie)) { + return s; + } + } + } + return null; + } + + public ShortcutTool getActiveShortcut() { + return currentShortcut; + } + + public boolean canActivateShortcut(KeyInputEvent kie) { + return getActivableShortcut(kie) != null; + } + + public boolean activateShortcut(KeyInputEvent kie) { + currentShortcut = getActivableShortcut(kie); + return isActive(); + } + + public void doKeyPressed(KeyInputEvent kie) { + ///todo check commande key + if (isActive()) { + currentShortcut.keyPressed(kie); + } + } + + /* + STATIC + */ + public static boolean checkEnterHit(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_RETURN) { + return true; + } + return false; + } + + public static boolean checkEscHit(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_ESCAPE) { + return true; + } + return false; + } + + public static boolean checkCtrlHit(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_LCONTROL || kie.getKeyCode() == KeyInput.KEY_RCONTROL) { + return true; + } + return false; + } + + public static boolean checkShiftHit(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_LSHIFT || kie.getKeyCode() == KeyInput.KEY_RSHIFT) { + return true; + } + return false; + } + + public static boolean checkAltHit(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_LMENU || kie.getKeyCode() == KeyInput.KEY_RMENU) { + return true; + } + return false; + } + + public static boolean checkNumberKey(KeyInputEvent kie, StringBuilder numberBuilder) { + if (kie.getKeyCode() == KeyInput.KEY_MINUS) { + if (numberBuilder.length() > 0) { + if (numberBuilder.charAt(0) == '-') { + numberBuilder.replace(0, 1, ""); + } else { + numberBuilder.insert(0, '-'); + } + } else { + numberBuilder.append('-'); + } + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_0 || kie.getKeyCode() == KeyInput.KEY_NUMPAD0) { + numberBuilder.append('0'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_1 || kie.getKeyCode() == KeyInput.KEY_NUMPAD1) { + numberBuilder.append('1'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_2 || kie.getKeyCode() == KeyInput.KEY_NUMPAD2) { + numberBuilder.append('2'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_3 || kie.getKeyCode() == KeyInput.KEY_NUMPAD3) { + numberBuilder.append('3'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_4 || kie.getKeyCode() == KeyInput.KEY_NUMPAD4) { + numberBuilder.append('4'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_5 || kie.getKeyCode() == KeyInput.KEY_NUMPAD5) { + numberBuilder.append('5'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_6 || kie.getKeyCode() == KeyInput.KEY_NUMPAD6) { + numberBuilder.append('6'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_7 || kie.getKeyCode() == KeyInput.KEY_NUMPAD7) { + numberBuilder.append('7'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_8 || kie.getKeyCode() == KeyInput.KEY_NUMPAD8) { + numberBuilder.append('8'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_9 || kie.getKeyCode() == KeyInput.KEY_NUMPAD9) { + numberBuilder.append('9'); + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_PERIOD) { + if (numberBuilder.indexOf(".") == -1) { // if it doesn't exist yet + if (numberBuilder.length() == 0 + || (numberBuilder.length() == 1 && numberBuilder.charAt(0) == '-')) { + numberBuilder.append("0."); + } else { + numberBuilder.append("."); + } + } + return true; + } + + return false; + } + + public static float getNumberkey(StringBuilder numberBuilder) { + if (numberBuilder.length() == 0) { + return 0; + } else { + return new Float(numberBuilder.toString()); + } + } + + public static boolean checkAxisKey(KeyInputEvent kie, Vector3f axisStore) { + if (kie.getKeyCode() == KeyInput.KEY_X) { + axisStore = Vector3f.UNIT_X; + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_Y) { + axisStore = Vector3f.UNIT_Y; + return true; + } else if (kie.getKeyCode() == KeyInput.KEY_Z) { + axisStore = Vector3f.UNIT_Z; + return true; + } + return false; + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutTool.java new file mode 100644 index 000000000..9b094fc1d --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutTool.java @@ -0,0 +1,29 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.gde.scenecomposer.SceneEditTool; +import com.jme3.input.event.KeyInputEvent; +import org.openide.util.Lookup; + +/** + * + * @author dokthar + */ +public abstract class ShortcutTool extends SceneEditTool { + + public abstract boolean isActivableBy(KeyInputEvent kie); + + public abstract void cancel(); + + protected final void terminate() { + Lookup.getDefault().lookup(ShortcutManager.class).terminate(); + } + + @Override + public abstract void keyPressed(KeyInputEvent kie); + +} From 9a8aa3b394a6ff6d3c5fdb00c41a6b40484fcb22 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Mon, 11 May 2015 02:28:56 -0400 Subject: [PATCH 175/225] Prevent an NPE for null parameter arrays. --- .../java/com/jme3/network/service/rpc/msg/RpcCallMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 70f12f1e0..11c5db591 100644 --- 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 @@ -92,7 +92,7 @@ public class RpcCallMessage extends AbstractMessage { + (isAsync() ? ", async" : ", sync") + ", objId=" + objId + ", procId=" + procId - + ", args.length=" + args.length + + ", args.length=" + (args == null ? 0 : args.length) + "]"; } } From 5c35b9bb22547beed083d9cde540c9dbf927ba7a Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 11 May 2015 19:31:10 -0400 Subject: [PATCH 176/225] Renderer: delete deprecated renderers --- .../renderer/android/OGLESShaderRenderer.java | 2549 ---------------- .../jme3/renderer/lwjgl/LwjglRenderer.java | 2695 ----------------- .../com/jme3/renderer/lwjgl/TextureUtil.java | 514 ---- 3 files changed, 5758 deletions(-) delete mode 100644 jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java delete mode 100644 jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java delete mode 100644 jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java deleted file mode 100644 index 462715f37..000000000 --- a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java +++ /dev/null @@ -1,2549 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.renderer.android; - -import android.opengl.GLES20; -import android.os.Build; -import com.jme3.asset.AndroidImageInfo; -import com.jme3.light.LightList; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.math.Vector4f; -import com.jme3.renderer.Caps; -import com.jme3.renderer.IDList; -import com.jme3.renderer.RenderContext; -import com.jme3.renderer.Renderer; -import com.jme3.renderer.RendererException; -import com.jme3.renderer.Statistics; -import com.jme3.renderer.android.TextureUtil.AndroidGLImageFormat; -import com.jme3.renderer.opengl.GLRenderer; -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.Buffer; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; -import java.util.EnumSet; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import jme3tools.shader.ShaderDebug; - -/** - * @deprecated Should not be used anymore. Use {@link GLRenderer} instead. - */ -@Deprecated -public class OGLESShaderRenderer implements Renderer { - - private static final Logger logger = Logger.getLogger(OGLESShaderRenderer.class.getName()); - private static final boolean VALIDATE_SHADER = false; - private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); - private final StringBuilder stringBuf = new StringBuilder(250); - private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); - private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); - private final RenderContext context = new RenderContext(); - private final NativeObjectManager objManager = new NativeObjectManager(); - private final EnumSet caps = EnumSet.noneOf(Caps.class); - // current state - private Shader boundShader; - // initalDrawBuf and initialReadBuf are not used on ES, - // http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindFramebuffer.xml - //private int initialDrawBuf, initialReadBuf; - private int glslVer; - private int vertexTextureUnits; - private int fragTextureUnits; - private int vertexUniforms; - private int fragUniforms; - private int vertexAttribs; -// private int maxFBOSamples; - 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 int maxRBSize; - private int maxTexSize; - private int maxCubeTexSize; - private int maxVertCount; - private int maxTriCount; - private boolean tdc; - private FrameBuffer lastFb = null; - private FrameBuffer mainFbOverride = null; - private final Statistics statistics = new Statistics(); - private int vpX, vpY, vpW, vpH; - private int clipX, clipY, clipW, clipH; - //private final GL10 gl; - private boolean powerVr = false; - private boolean useVBO = false; - - public OGLESShaderRenderer() { - } - - 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 Statistics getStatistics() { - return statistics; - } - - public EnumSet getCaps() { - return caps; - } - - private static final Pattern VERSION = Pattern.compile(".*?(\\d+)\\.(\\d+).*"); - - public static int extractVersion(String version) { - - Matcher m = VERSION.matcher(version); - if (m.matches()) { - int major = Integer.parseInt(m.group(1)); - int minor = Integer.parseInt(m.group(2)); - - return major * 100 + minor * 10; - } else { - return -1; - } - } - - public void initialize() { - logger.log(Level.FINE, "Vendor: {0}", GLES20.glGetString(GLES20.GL_VENDOR)); - logger.log(Level.FINE, "Renderer: {0}", GLES20.glGetString(GLES20.GL_RENDERER)); - logger.log(Level.FINE, "Version: {0}", GLES20.glGetString(GLES20.GL_VERSION)); - logger.log(Level.FINE, "Shading Language Version: {0}", GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION)); - - powerVr = GLES20.glGetString(GLES20.GL_RENDERER).contains("PowerVR"); - - - //workaround, always assume we support GLSL100 - //some cards just don't report this correctly - caps.add(Caps.GLSL100); - - /* - // 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(GLES20.glGetString(GLES20.GL_VERSION)); - if (openGlVer == -1) { - glslVer = -1; - throw new UnsupportedOperationException("OpenGL ES 2.0+ is required for OGLESShaderRenderer!"); - } - - // Check shader language version - glslVer = extractVersion(GLES20.glGetString(GLES20.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; - } - - GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); - vertexTextureUnits = intBuf16.get(0); - logger.log(Level.FINE, "VTF Units: {0}", vertexTextureUnits); - if (vertexTextureUnits > 0) { - caps.add(Caps.VertexTextureFetch); - } - - GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); - fragTextureUnits = intBuf16.get(0); - logger.log(Level.FINE, "Texture Units: {0}", fragTextureUnits); - - // Multiply vector count by 4 to get float count. - GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_UNIFORM_VECTORS, intBuf16); - vertexUniforms = intBuf16.get(0) * 4; - logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); - - GLES20.glGetIntegerv(GLES20.GL_MAX_FRAGMENT_UNIFORM_VECTORS, intBuf16); - fragUniforms = intBuf16.get(0) * 4; - logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - - GLES20.glGetIntegerv(GLES20.GL_MAX_VARYING_VECTORS, intBuf16); - int varyingFloats = intBuf16.get(0) * 4; - logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); - - GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_ATTRIBS, intBuf16); - vertexAttribs = intBuf16.get(0); - logger.log(Level.FINE, "Vertex Attributes: {0}", vertexAttribs); - - GLES20.glGetIntegerv(GLES20.GL_SUBPIXEL_BITS, intBuf16); - int subpixelBits = intBuf16.get(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); - - GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, intBuf16); - maxTexSize = intBuf16.get(0); - logger.log(Level.FINE, "Maximum Texture Resolution: {0}", maxTexSize); - - GLES20.glGetIntegerv(GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); - maxCubeTexSize = intBuf16.get(0); - logger.log(Level.FINE, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); - - GLES20.glGetIntegerv(GLES20.GL_MAX_RENDERBUFFER_SIZE, intBuf16); - maxRBSize = intBuf16.get(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 = GLES20.glGetString(GLES20.GL_EXTENSIONS); - logger.log(Level.FINE, "GL_EXTENSIONS: {0}", extensions); - - // Get number of compressed formats available. - GLES20.glGetIntegerv(GLES20.GL_NUM_COMPRESSED_TEXTURE_FORMATS, intBuf16); - int numCompressedFormats = intBuf16.get(0); - - // Allocate buffer for compressed formats. - IntBuffer compressedFormats = BufferUtils.createIntBuffer(numCompressedFormats); - GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats); - - // Check for errors after all glGet calls. - RendererUtil.checkGLError(); - - // Print compressed formats. - for (int i = 0; i < numCompressedFormats; i++) { - logger.log(Level.FINE, "Compressed Texture Formats: {0}", compressedFormats.get(i)); - } - - TextureUtil.loadTextureFeatures(extensions); - - applyRenderState(RenderState.DEFAULT); - GLES20.glDisable(GLES20.GL_DITHER); - RendererUtil.checkGLError(); - - useVBO = false; - - // NOTE: SDK_INT is only available since 1.6, - // but for jME3 it doesn't matter since android versions 1.5 and below - // are not supported. - if (Build.VERSION.SDK_INT >= 9){ - logger.log(Level.FINE, "Force-enabling VBO (Android 2.3 or higher)"); - useVBO = true; - } else { - useVBO = false; - } - - logger.log(Level.FINE, "Caps: {0}", caps); - } - - /** - * resetGLObjects should be called when die GLView gets recreated to reset all GPU objects - */ - public void resetGLObjects() { - objManager.resetObjects(); - statistics.clearMemory(); - boundShader = null; - lastFb = null; - context.reset(); - } - - public void cleanup() { - objManager.deleteAllObjects(this); - statistics.clearMemory(); - } - - private void checkCap(Caps cap) { - if (!caps.contains(cap)) { - throw new UnsupportedOperationException("Required capability missing: " + cap.name()); - } - } - - /*********************************************************************\ - |* Render State *| - \*********************************************************************/ - public void setDepthRange(float start, float end) { - GLES20.glDepthRangef(start, end); - RendererUtil.checkGLError(); - } - - public void clearBuffers(boolean color, boolean depth, boolean stencil) { - 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) { - GLES20.glColorMask(true, true, true, true); - context.colorWriteEnabled = true; - } - bits = GLES20.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) { - GLES20.glDepthMask(true); - context.depthWriteEnabled = true; - } - bits |= GLES20.GL_DEPTH_BUFFER_BIT; - } - if (stencil) { - bits |= GLES20.GL_STENCIL_BUFFER_BIT; - } - if (bits != 0) { - GLES20.glClear(bits); - RendererUtil.checkGLError(); - } - } - - public void setBackgroundColor(ColorRGBA color) { - GLES20.glClearColor(color.r, color.g, color.b, color.a); - RendererUtil.checkGLError(); - } - - public void applyRenderState(RenderState state) { - /* - 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) { - GLES20.glEnable(GLES20.GL_DEPTH_TEST); - GLES20.glDepthFunc(convertTestFunction(context.depthFunc)); - RendererUtil.checkGLError(); - context.depthTestEnabled = true; - } else if (!state.isDepthTest() && context.depthTestEnabled) { - GLES20.glDisable(GLES20.GL_DEPTH_TEST); - RendererUtil.checkGLError(); - context.depthTestEnabled = false; - } - if (state.getDepthFunc() != context.depthFunc) { - GLES20.glDepthFunc(convertTestFunction(state.getDepthFunc())); - context.depthFunc = state.getDepthFunc(); - } - - if (state.isDepthWrite() && !context.depthWriteEnabled) { - GLES20.glDepthMask(true); - RendererUtil.checkGLError(); - context.depthWriteEnabled = true; - } else if (!state.isDepthWrite() && context.depthWriteEnabled) { - GLES20.glDepthMask(false); - RendererUtil.checkGLError(); - context.depthWriteEnabled = false; - } - if (state.isColorWrite() && !context.colorWriteEnabled) { - GLES20.glColorMask(true, true, true, true); - RendererUtil.checkGLError(); - context.colorWriteEnabled = true; - } else if (!state.isColorWrite() && context.colorWriteEnabled) { - GLES20.glColorMask(false, false, false, false); - RendererUtil.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) { - GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL); - GLES20.glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - RendererUtil.checkGLError(); - - context.polyOffsetEnabled = true; - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } else { - if (state.getPolyOffsetFactor() != context.polyOffsetFactor - || state.getPolyOffsetUnits() != context.polyOffsetUnits) { - GLES20.glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - RendererUtil.checkGLError(); - - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } - } - } else { - if (context.polyOffsetEnabled) { - GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL); - RendererUtil.checkGLError(); - - context.polyOffsetEnabled = false; - context.polyOffsetFactor = 0; - context.polyOffsetUnits = 0; - } - } - if (state.getFaceCullMode() != context.cullMode) { - if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { - GLES20.glDisable(GLES20.GL_CULL_FACE); - RendererUtil.checkGLError(); - } else { - GLES20.glEnable(GLES20.GL_CULL_FACE); - RendererUtil.checkGLError(); - } - - switch (state.getFaceCullMode()) { - case Off: - break; - case Back: - GLES20.glCullFace(GLES20.GL_BACK); - RendererUtil.checkGLError(); - break; - case Front: - GLES20.glCullFace(GLES20.GL_FRONT); - RendererUtil.checkGLError(); - break; - case FrontAndBack: - GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK); - RendererUtil.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) { - GLES20.glDisable(GLES20.GL_BLEND); - RendererUtil.checkGLError(); - } else { - GLES20.glEnable(GLES20.GL_BLEND); - switch (state.getBlendMode()) { - case Off: - break; - case Additive: - GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); - break; - case AlphaAdditive: - GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); - break; - case Color: - GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR); - break; - case Alpha: - GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); - break; - case PremultAlpha: - GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); - break; - case Modulate: - GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO); - break; - case ModulateX2: - GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR); - break; - case Screen: - GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR); - break; - case Exclusion: - GLES20.glBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR); - break; - default: - throw new UnsupportedOperationException("Unrecognized blend mode: " - + state.getBlendMode()); - } - RendererUtil.checkGLError(); - } - context.blendMode = state.getBlendMode(); - } - } - - /*********************************************************************\ - |* Camera and World transforms *| - \*********************************************************************/ - public void setViewPort(int x, int y, int w, int h) { - if (x != vpX || vpY != y || vpW != w || vpH != h) { - GLES20.glViewport(x, y, w, h); - RendererUtil.checkGLError(); - - vpX = x; - vpY = y; - vpW = w; - vpH = h; - } - } - - public void setClipRect(int x, int y, int width, int height) { - if (!context.clipRectEnabled) { - GLES20.glEnable(GLES20.GL_SCISSOR_TEST); - RendererUtil.checkGLError(); - context.clipRectEnabled = true; - } - if (clipX != x || clipY != y || clipW != width || clipH != height) { - GLES20.glScissor(x, y, width, height); - RendererUtil.checkGLError(); - clipX = x; - clipY = y; - clipW = width; - clipH = height; - } - } - - public void clearClipRect() { - if (context.clipRectEnabled) { - GLES20.glDisable(GLES20.GL_SCISSOR_TEST); - RendererUtil.checkGLError(); - context.clipRectEnabled = false; - - clipX = 0; - clipY = 0; - clipW = 0; - clipH = 0; - } - } - - public void postFrame() { - RendererUtil.checkGLErrorForced(); - - objManager.deleteUnused(this); - } - - /*********************************************************************\ - |* Shaders *| - \*********************************************************************/ - protected void updateUniformLocation(Shader shader, Uniform uniform) { - stringBuf.setLength(0); - stringBuf.append(uniform.getName()).append('\0'); - updateNameBuffer(); - int loc = GLES20.glGetUniformLocation(shader.getId(), uniform.getName()); - RendererUtil.checkGLError(); - - if (loc < 0) { - uniform.setLocation(-1); - // uniform is not declared in shader - } else { - uniform.setLocation(loc); - } - } - - protected void bindProgram(Shader shader) { - int shaderId = shader.getId(); - if (context.boundShaderProgram != shaderId) { - GLES20.glUseProgram(shaderId); - RendererUtil.checkGLError(); - - statistics.onShaderUse(shader, true); - boundShader = shader; - context.boundShaderProgram = shaderId; - } else { - statistics.onShaderUse(shader, false); - } - } - - protected void updateUniform(Shader shader, Uniform uniform) { - assert uniform.getName() != null; - assert shader.getId() > 0; - - bindProgram(shader); - - 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(); - FloatBuffer fb; - IntBuffer ib; - switch (uniform.getVarType()) { - case Float: - Float f = (Float) uniform.getValue(); - GLES20.glUniform1f(loc, f.floatValue()); - break; - case Vector2: - Vector2f v2 = (Vector2f) uniform.getValue(); - GLES20.glUniform2f(loc, v2.getX(), v2.getY()); - break; - case Vector3: - Vector3f v3 = (Vector3f) uniform.getValue(); - GLES20.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); - break; - case Vector4: - Object val = uniform.getValue(); - if (val instanceof ColorRGBA) { - ColorRGBA c = (ColorRGBA) val; - GLES20.glUniform4f(loc, c.r, c.g, c.b, c.a); - } else if (val instanceof Vector4f) { - Vector4f c = (Vector4f) val; - GLES20.glUniform4f(loc, c.x, c.y, c.z, c.w); - } else { - Quaternion c = (Quaternion) uniform.getValue(); - GLES20.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); - } - break; - case Boolean: - Boolean b = (Boolean) uniform.getValue(); - GLES20.glUniform1i(loc, b.booleanValue() ? GLES20.GL_TRUE : GLES20.GL_FALSE); - break; - case Matrix3: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 9; - GLES20.glUniformMatrix3fv(loc, 1, false, fb); - break; - case Matrix4: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 16; - GLES20.glUniformMatrix4fv(loc, 1, false, fb); - break; - case IntArray: - ib = (IntBuffer) uniform.getValue(); - GLES20.glUniform1iv(loc, ib.limit(), ib); - break; - case FloatArray: - fb = (FloatBuffer) uniform.getValue(); - GLES20.glUniform1fv(loc, fb.limit(), fb); - break; - case Vector2Array: - fb = (FloatBuffer) uniform.getValue(); - GLES20.glUniform2fv(loc, fb.limit() / 2, fb); - break; - case Vector3Array: - fb = (FloatBuffer) uniform.getValue(); - GLES20.glUniform3fv(loc, fb.limit() / 3, fb); - break; - case Vector4Array: - fb = (FloatBuffer) uniform.getValue(); - GLES20.glUniform4fv(loc, fb.limit() / 4, fb); - break; - case Matrix4Array: - fb = (FloatBuffer) uniform.getValue(); - GLES20.glUniformMatrix4fv(loc, fb.limit() / 16, false, fb); - break; - case Int: - Integer i = (Integer) uniform.getValue(); - GLES20.glUniform1i(loc, i.intValue()); - break; - default: - throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); - } - RendererUtil.checkGLError(); - } - - 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 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 - } - } - - /* - * (Non-javadoc) - * Only used for fixed-function. Ignored. - */ - public void setLighting(LightList list) { - } - - public int convertShaderType(ShaderType type) { - switch (type) { - case Fragment: - return GLES20.GL_FRAGMENT_SHADER; - case Vertex: - return GLES20.GL_VERTEX_SHADER; -// case Geometry: -// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; - default: - throw new RuntimeException("Unrecognized shader type."); - } - } - - public void updateShaderSourceData(ShaderSource source) { - int id = source.getId(); - if (id == -1) { - // Create id - id = GLES20.glCreateShader(convertShaderType(source.getType())); - RendererUtil.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. - GLES20.glShaderSource( - id, source.getDefines() - + source.getSource()); - } else { - String precision =""; - if (source.getType() == ShaderType.Fragment) { - precision = "precision mediump float;\n"; - } - GLES20.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]); - - GLES20.glCompileShader(id); - RendererUtil.checkGLError(); - - GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, intBuf1); - RendererUtil.checkGLError(); - - boolean compiledOK = intBuf1.get(0) == GLES20.GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !compiledOK) { - // even if compile succeeded, check - // log for warnings - GLES20.glGetShaderiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); - RendererUtil.checkGLError(); - infoLog = GLES20.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 void updateShaderData(Shader shader) { - int id = shader.getId(); - boolean needRegister = false; - if (id == -1) { - // create program - id = GLES20.glCreateProgram(); - RendererUtil.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); - } - - GLES20.glAttachShader(id, source.getId()); - RendererUtil.checkGLError(); - } - - // link shaders to program - GLES20.glLinkProgram(id); - RendererUtil.checkGLError(); - - GLES20.glGetProgramiv(id, GLES20.GL_LINK_STATUS, intBuf1); - RendererUtil.checkGLError(); - - boolean linkOK = intBuf1.get(0) == GLES20.GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !linkOK) { - GLES20.glGetProgramiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); - RendererUtil.checkGLError(); - - int length = intBuf1.get(0); - if (length > 3) { - // get infos - infoLog = GLES20.glGetProgramInfoLog(id); - RendererUtil.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: "); - } - } - } - - public void setShader(Shader shader) { - 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); - } - } - - public void deleteShaderSource(ShaderSource source) { - if (source.getId() < 0) { - logger.warning("Shader source is not uploaded to GPU, cannot delete."); - return; - } - - source.clearUpdateNeeded(); - - GLES20.glDeleteShader(source.getId()); - RendererUtil.checkGLError(); - - source.resetObject(); - } - - public void deleteShader(Shader shader) { - if (shader.getId() == -1) { - logger.warning("Shader is not uploaded to GPU, cannot delete."); - return; - } - - for (ShaderSource source : shader.getSources()) { - if (source.getId() != -1) { - GLES20.glDetachShader(shader.getId(), source.getId()); - RendererUtil.checkGLError(); - - deleteShaderSource(source); - } - } - - GLES20.glDeleteProgram(shader.getId()); - RendererUtil.checkGLError(); - - statistics.onDeleteShader(); - shader.resetObject(); - } - - private int convertTestFunction(RenderState.TestFunction testFunc) { - switch (testFunc) { - case Never: - return GLES20.GL_NEVER; - case Less: - return GLES20.GL_LESS; - case LessOrEqual: - return GLES20.GL_LEQUAL; - case Greater: - return GLES20.GL_GREATER; - case GreaterOrEqual: - return GLES20.GL_GEQUAL; - case Equal: - return GLES20.GL_EQUAL; - case NotEqual: - return GLES20.GL_NOTEQUAL; - case Always: - return GLES20.GL_ALWAYS; - default: - throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); - } - } - - /*********************************************************************\ - |* Framebuffers *| - \*********************************************************************/ - - public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { - throw new RendererException("Copy framebuffer not implemented yet."); - -// if (GLContext.getCapabilities().GL_EXT_framebuffer_blit) { -// int srcX0 = 0; -// int srcY0 = 0; -// int srcX1 = 0; -// int srcY1 = 0; -// -// int dstX0 = 0; -// int dstY0 = 0; -// int dstX1 = 0; -// int dstY1 = 0; -// -// int prevFBO = context.boundFBO; -// -// if (mainFbOverride != null) { -// if (src == null) { -// src = mainFbOverride; -// } -// if (dst == null) { -// dst = mainFbOverride; -// } -// } -// -// if (src != null && src.isUpdateNeeded()) { -// updateFrameBuffer(src); -// } -// -// if (dst != null && dst.isUpdateNeeded()) { -// updateFrameBuffer(dst); -// } -// -// if (src == null) { -// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); -// srcX0 = vpX; -// srcY0 = vpY; -// srcX1 = vpX + vpW; -// srcY1 = vpY + vpH; -// } else { -// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, src.getId()); -// srcX1 = src.getWidth(); -// srcY1 = src.getHeight(); -// } -// if (dst == null) { -// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); -// dstX0 = vpX; -// dstY0 = vpY; -// dstX1 = vpX + vpW; -// dstY1 = vpY + vpH; -// } else { -// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, dst.getId()); -// dstX1 = dst.getWidth(); -// dstY1 = dst.getHeight(); -// } -// -// -// int mask = GL_COLOR_BUFFER_BIT; -// if (copyDepth) { -// mask |= GL_DEPTH_BUFFER_BIT; -// } -// GLES20.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, -// dstX0, dstY0, dstX1, dstY1, mask, -// GL_NEAREST); -// -// -// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, prevFBO); -// try { -// checkFrameBufferError(); -// } catch (IllegalStateException ex) { -// logger.log(Level.SEVERE, "Source FBO:\n{0}", src); -// logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); -// throw ex; -// } -// } else { -// throw new RendererException("EXT_framebuffer_blit required."); -// // TODO: support non-blit copies? -// } - } - - 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 = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - switch (status) { - case GLES20.GL_FRAMEBUFFER_COMPLETE: - break; - case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: - //Choose different formats - throw new IllegalStateException("Framebuffer object format is " - + "unsupported by the video hardware."); - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - throw new IllegalStateException("Framebuffer has erronous attachment."); - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); - case GLES20.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? " + GLES20.glIsRenderbuffer(rb.getId())); - - int attachment = convertAttachmentSlot(rb.getSlot()); - - intBuf16.clear(); - GLES20.glGetFramebufferAttachmentParameteriv(GLES20.GL_FRAMEBUFFER, - attachment, GLES20.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, intBuf16); - int type = intBuf16.get(0); - - intBuf16.clear(); - GLES20.glGetFramebufferAttachmentParameteriv(GLES20.GL_FRAMEBUFFER, - attachment, GLES20.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, intBuf16); - int rbName = intBuf16.get(0); - - switch (type) { - case GLES20.GL_NONE: - System.out.println("Type: None"); - break; - case GLES20.GL_TEXTURE: - System.out.println("Type: Texture"); - break; - case GLES20.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? " + GLES20.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) { - GLES20.glBindFramebuffer(GLES20.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); - } - } - - private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - int id = rb.getId(); - if (id == -1) { - GLES20.glGenRenderbuffers(1, intBuf1); - RendererUtil.checkGLError(); - - id = intBuf1.get(0); - rb.setId(id); - } - - if (context.boundRB != id) { - GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, id); - RendererUtil.checkGLError(); - - context.boundRB = id; - } - - if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { - throw new RendererException("Resolution " + fb.getWidth() - + ":" + fb.getHeight() + " is not supported."); - } - - AndroidGLImageFormat imageFormat = TextureUtil.getImageFormat(rb.getFormat(), true); - 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 { - GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, - imageFormat.renderBufferStorageFormat, - fb.getWidth(), - fb.getHeight()); - - RendererUtil.checkGLError(); - } - } - - private int convertAttachmentSlot(int attachmentSlot) { - // can also add support for stencil here - if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { - return GLES20.GL_DEPTH_ATTACHMENT; -// if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) { -// return GLES30.GL_DEPTH_STENCIL_ATTACHMENT; - } else if (attachmentSlot == 0) { - return GLES20.GL_COLOR_ATTACHMENT0; - } else { - throw new UnsupportedOperationException("Android does not support multiple color attachments to an FBO"); - } - } - - 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); - } - - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, - convertAttachmentSlot(rb.getSlot()), - convertTextureType(tex.getType()), - image.getId(), - 0); - - RendererUtil.checkGLError(); - } - - 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) { - GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, - convertAttachmentSlot(rb.getSlot()), - GLES20.GL_RENDERBUFFER, - rb.getId()); - - RendererUtil.checkGLError(); - } - } - - public void updateFrameBuffer(FrameBuffer fb) { - int id = fb.getId(); - if (id == -1) { - intBuf1.clear(); - // create FBO - GLES20.glGenFramebuffers(1, intBuf1); - RendererUtil.checkGLError(); - - id = intBuf1.get(0); - fb.setId(id); - objManager.registerObject(fb); - - statistics.onNewFrameBuffer(); - } - - if (context.boundFBO != id) { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, id); - RendererUtil.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 setMainFrameBufferOverride(FrameBuffer fb){ - mainFbOverride = fb; - } - - public void setFrameBuffer(FrameBuffer fb) { - 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()); - GLES20.glGenerateMipmap(textureType); - RendererUtil.checkGLError(); - } - } - } - - if (fb == null) { - // unbind any fbos - if (context.boundFBO != 0) { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - RendererUtil.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()) { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb.getId()); - RendererUtil.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 < fb.getNumColorBuffers(); i++) { - intBuf16.put(GLES20.GL_COLOR_ATTACHMENT0 + i); - } - - intBuf16.flip(); -// glDrawBuffers(intBuf16); - context.boundDrawBuf = 100 + fb.getNumColorBuffers(); - } - } else { - RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); - // select this draw buffer - if (context.boundDrawBuf != rb.getSlot()) { - GLES20.glActiveTexture(convertAttachmentSlot(rb.getSlot())); - RendererUtil.checkGLError(); - - context.boundDrawBuf = rb.getSlot(); - } - } - } - - assert fb.getId() >= 0; - assert context.boundFBO == fb.getId(); - - lastFb = fb; - - checkFrameBufferStatus(fb); - } - } - - /** - * Reads the Color Buffer from OpenGL and stores into the ByteBuffer. - * Make sure to call setViewPort with the appropriate viewport size before - * calling readFrameBuffer. - * @param fb FrameBuffer - * @param byteBuf ByteBuffer to store the Color Buffer from OpenGL - */ - public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { - 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); - } - - GLES20.glReadPixels(vpX, vpY, vpW, vpH, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuf); - RendererUtil.checkGLError(); - } - - private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - intBuf1.put(0, rb.getId()); - GLES20.glDeleteRenderbuffers(1, intBuf1); - RendererUtil.checkGLError(); - } - - public void deleteFrameBuffer(FrameBuffer fb) { - if (fb.getId() != -1) { - if (context.boundFBO == fb.getId()) { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - RendererUtil.checkGLError(); - - context.boundFBO = 0; - } - - if (fb.getDepthBuffer() != null) { - deleteRenderBuffer(fb, fb.getDepthBuffer()); - } - if (fb.getColorBuffer() != null) { - deleteRenderBuffer(fb, fb.getColorBuffer()); - } - - intBuf1.put(0, fb.getId()); - GLES20.glDeleteFramebuffers(1, intBuf1); - RendererUtil.checkGLError(); - - fb.resetObject(); - - statistics.onDeleteFrameBuffer(); - } - } - - /*********************************************************************\ - |* Textures *| - \*********************************************************************/ - private int convertTextureType(Texture.Type type) { - switch (type) { - case TwoDimensional: - return GLES20.GL_TEXTURE_2D; - // case TwoDimensionalArray: - // return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT; -// case ThreeDimensional: - // return GLES20.GL_TEXTURE_3D; - case CubeMap: - return GLES20.GL_TEXTURE_CUBE_MAP; - default: - throw new UnsupportedOperationException("Unknown texture type: " + type); - } - } - - private int convertMagFilter(Texture.MagFilter filter) { - switch (filter) { - case Bilinear: - return GLES20.GL_LINEAR; - case Nearest: - return GLES20.GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown mag filter: " + filter); - } - } - - private int convertMinFilter(Texture.MinFilter filter) { - switch (filter) { - case Trilinear: - return GLES20.GL_LINEAR_MIPMAP_LINEAR; - case BilinearNearestMipMap: - return GLES20.GL_LINEAR_MIPMAP_NEAREST; - case NearestLinearMipMap: - return GLES20.GL_NEAREST_MIPMAP_LINEAR; - case NearestNearestMipMap: - return GLES20.GL_NEAREST_MIPMAP_NEAREST; - case BilinearNoMipMaps: - return GLES20.GL_LINEAR; - case NearestNoMipMaps: - return GLES20.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 GLES20.GL_CLAMP_TO_EDGE; - case Repeat: - return GLES20.GL_REPEAT; - case MirroredRepeat: - return GLES20.GL_MIRRORED_REPEAT; - default: - throw new UnsupportedOperationException("Unknown wrap mode: " + mode); - } - } - - /** - * setupTextureParams sets the OpenGL context texture parameters - * @param tex the Texture to set the texture parameters from - */ - private void setupTextureParams(Texture tex) { - int target = convertTextureType(tex.getType()); - - // filter things - int minFilter = convertMinFilter(tex.getMinFilter()); - int magFilter = convertMagFilter(tex.getMagFilter()); - - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MIN_FILTER, minFilter); - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MAG_FILTER, magFilter); - RendererUtil.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: - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); - - // fall down here is intentional.. -// case OneDimensional: - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); - - RendererUtil.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); - } - } - */ - } - - /** - * activates and binds the texture - * @param img - * @param type - */ - public void updateTexImageData(Image img, Texture.Type type) { - int texId = img.getId(); - if (texId == -1) { - // create texture - GLES20.glGenTextures(1, intBuf1); - RendererUtil.checkGLError(); - - texId = intBuf1.get(0); - img.setId(texId); - objManager.registerObject(img); - - statistics.onNewTexture(); - } - - // bind texture - int target = convertTextureType(type); - if (context.boundTextures[0] != img) { - if (context.boundTextureUnit != 0) { - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - RendererUtil.checkGLError(); - - context.boundTextureUnit = 0; - } - - GLES20.glBindTexture(target, texId); - RendererUtil.checkGLError(); - - context.boundTextures[0] = img; - } - - boolean needMips = false; - if (img.isGeneratedMipmapsRequired()) { - needMips = true; - img.setMipmapsGenerated(true); - } - - if (target == GLES20.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 == GLES20.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(GLES20.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, GLES20.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(); - } - - public void setTexture(int unit, Texture tex) { - 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 (!context.textureIndexList.moveToNew(unit)) { -// if (context.boundTextureUnit != unit){ -// glActiveTexture(GL_TEXTURE0 + unit); -// context.boundTextureUnit = unit; -// } -// glEnable(type); -// } - - if (textures[unit] != image) { - if (context.boundTextureUnit != unit) { - GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit); - context.boundTextureUnit = unit; - } - - GLES20.glBindTexture(type, texId); - RendererUtil.checkGLError(); - - textures[unit] = image; - - statistics.onTextureUse(tex.getImage(), true); - } else { - statistics.onTextureUse(tex.getImage(), false); - } - - setupTextureParams(tex); - } - - public void modifyTexture(Texture tex, Image pixels, int x, int y) { - setTexture(0, tex); - TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); - } - - public void deleteImage(Image image) { - int texId = image.getId(); - if (texId != -1) { - intBuf1.put(0, texId); - intBuf1.position(0).limit(1); - - GLES20.glDeleteTextures(1, intBuf1); - RendererUtil.checkGLError(); - - image.resetObject(); - - statistics.onDeleteTexture(); - } - } - - /*********************************************************************\ - |* Vertex Buffers and Attributes *| - \*********************************************************************/ - private int convertUsage(Usage usage) { - switch (usage) { - case Static: - return GLES20.GL_STATIC_DRAW; - case Dynamic: - return GLES20.GL_DYNAMIC_DRAW; - case Stream: - return GLES20.GL_STREAM_DRAW; - default: - throw new RuntimeException("Unknown usage type."); - } - } - - private int convertVertexBufferFormat(Format format) { - switch (format) { - case Byte: - return GLES20.GL_BYTE; - case UnsignedByte: - return GLES20.GL_UNSIGNED_BYTE; - case Short: - return GLES20.GL_SHORT; - case UnsignedShort: - return GLES20.GL_UNSIGNED_SHORT; - case Int: - return GLES20.GL_INT; - case UnsignedInt: - return GLES20.GL_UNSIGNED_INT; - /* - case Half: - return NVHalfFloat.GL_HALF_FLOAT_NV; - // return ARBHalfFloatVertex.GL_HALF_FLOAT; - */ - case Float: - return GLES20.GL_FLOAT; -// case Double: -// return GLES20.GL_DOUBLE; - default: - throw new RuntimeException("Unknown buffer format."); - - } - } - - public void updateBufferData(VertexBuffer vb) { - int bufId = vb.getId(); - boolean created = false; - if (bufId == -1) { - // create buffer - GLES20.glGenBuffers(1, intBuf1); - RendererUtil.checkGLError(); - - bufId = intBuf1.get(0); - vb.setId(bufId); - objManager.registerObject(vb); - - created = true; - } - - // bind buffer - int target; - if (vb.getBufferType() == VertexBuffer.Type.Index) { - target = GLES20.GL_ELEMENT_ARRAY_BUFFER; - if (context.boundElementArrayVBO != bufId) { - GLES20.glBindBuffer(target, bufId); - RendererUtil.checkGLError(); - - context.boundElementArrayVBO = bufId; - } - } else { - target = GLES20.GL_ARRAY_BUFFER; - if (context.boundArrayVBO != bufId) { - GLES20.glBindBuffer(target, bufId); - RendererUtil.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: - GLES20.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); - RendererUtil.checkGLError(); - break; - case Short: - case UnsignedShort: - GLES20.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); - RendererUtil.checkGLError(); - break; - case Int: - case UnsignedInt: - GLES20.glBufferData(target, size, (IntBuffer) vb.getData(), usage); - RendererUtil.checkGLError(); - break; - case Float: - GLES20.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); - RendererUtil.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: -// GLES20.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); -// RendererUtil.checkGLError(); -// break; -// case Short: -// case UnsignedShort: -// GLES20.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); -// RendererUtil.checkGLError(); -// break; -// case Int: -// case UnsignedInt: -// GLES20.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); -// RendererUtil.checkGLError(); -// break; -// case Float: -// GLES20.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); -// RendererUtil.checkGLError(); -// break; -// default: -// throw new RuntimeException("Unknown buffer format."); -// } -// } - vb.clearUpdateNeeded(); - } - - public void deleteBuffer(VertexBuffer vb) { - int bufId = vb.getId(); - if (bufId != -1) { - // delete buffer - intBuf1.put(0, bufId); - intBuf1.position(0).limit(1); - - GLES20.glDeleteBuffers(1, intBuf1); - RendererUtil.checkGLError(); - - vb.resetObject(); - } - } - - public void clearVertexAttribs() { - IDList attribList = context.attribIndexList; - for (int i = 0; i < attribList.oldLen; i++) { - int idx = attribList.oldList[i]; - - GLES20.glDisableVertexAttribArray(idx); - RendererUtil.checkGLError(); - - context.boundAttribs[idx] = null; - } - context.attribIndexList.copyNewToOld(); - } - - public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { - if (vb.getBufferType() == VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); - } - - if (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 = GLES20.glGetAttribLocation(programId, attributeName); - RendererUtil.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)) { - GLES20.glEnableVertexAttribArray(loc); - RendererUtil.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) { - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufId); - RendererUtil.checkGLError(); - - context.boundArrayVBO = bufId; - } - - vb.getData().rewind(); - - GLES20.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - 0); - - RendererUtil.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{*/ - GLES20.glDrawArrays(convertElementMode(mode), 0, vertCount); - RendererUtil.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) { - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufId); - RendererUtil.checkGLError(); - - context.boundElementArrayVBO = bufId; - } - - int vertCount = mesh.getVertexCount(); - boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - - Buffer indexData = indexBuf.getData(); - - if (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); - GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); - RendererUtil.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 { - indexData.rewind(); - GLES20.glDrawElements( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - 0); - RendererUtil.checkGLError(); - } - } - } - - /*********************************************************************\ - |* Render Calls *| - \*********************************************************************/ - public int convertElementMode(Mesh.Mode mode) { - switch (mode) { - case Points: - return GLES20.GL_POINTS; - case Lines: - return GLES20.GL_LINES; - case LineLoop: - return GLES20.GL_LINE_LOOP; - case LineStrip: - return GLES20.GL_LINE_STRIP; - case Triangles: - return GLES20.GL_TRIANGLES; - case TriangleFan: - return GLES20.GL_TRIANGLE_FAN; - case TriangleStrip: - return GLES20.GL_TRIANGLE_STRIP; - default: - throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); - } - } - - public void updateVertexArray(Mesh mesh) { - logger.log(Level.FINE, "updateVertexArray({0})", mesh); - int id = mesh.getId(); - /* - if (id == -1){ - IntBuffer temp = intBuf1; - // ARBVertexArrayObject.glGenVertexArrays(temp); - GLES20.glGenVertexArrays(temp); - id = temp.get(0); - mesh.setId(id); - } - - if (context.boundVertexArray != id){ - // ARBVertexArrayObject.glBindVertexArray(id); - GLES20.glBindVertexArray(id); - context.boundVertexArray = id; - } - */ - VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); - if (interleavedData != null && interleavedData.isUpdateNeeded()) { - updateBufferData(interleavedData); - } - - - 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); - } - } - } - - /** - * renderMeshVertexArray renders a mesh using vertex arrays - */ - 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; - 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 { - GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); - RendererUtil.checkGLError(); - } - clearVertexAttribs(); - } - - private void renderMeshDefault(Mesh mesh, int lod, int count) { - VertexBuffer indices; - 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"); - GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); - RendererUtil.checkGLError(); - } - clearVertexAttribs(); - } - - public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - 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()) { - GLES20.glLineWidth(mesh.getLineWidth()); - RendererUtil.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); - } - } - - /** - * drawTriangleList_Array uses Vertex Array - * @param indexBuf - * @param mesh - * @param count - */ - 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); - GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); - RendererUtil.checkGLError(); - - curOffset += elementLength * elSize; - } - } else { - GLES20.glDrawElements( - convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertVertexBufferFormat(indexBuf.getFormat()), - indexBuf.getData()); - RendererUtil.checkGLError(); - } - } - - /** - * setVertexAttrib_Array uses Vertex Array - * @param vb - * @param idb - */ - 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 = GLES20.glGetAttribLocation(programId, attributeName); - RendererUtil.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 - GLES20.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertVertexBufferFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - avb.getData()); - - RendererUtil.checkGLError(); - - GLES20.glEnableVertexAttribArray(loc); - RendererUtil.checkGLError(); - - attribs[loc] = vb; - } // if (attribs[loc] != vb) - } else { - throw new IllegalStateException("Cannot render mesh without shader bound"); - } - } - - /** - * setVertexAttrib_Array uses Vertex Array - * @param vb - */ - public void setVertexAttrib_Array(VertexBuffer vb) { - setVertexAttrib_Array(vb, null); - } - - public void setAlphaToCoverage(boolean value) { - if (value) { - GLES20.glEnable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); - RendererUtil.checkGLError(); - } else { - GLES20.glDisable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); - RendererUtil.checkGLError(); - } - } - - @Override - public void invalidateState() { - context.reset(); - boundShader = null; - lastFb = null; - } - - public void setMainFrameBufferSrgb(boolean srgb) { - //TODO once opglES3.0 is supported maybe.... - } - - public void setLinearizeSrgbImages(boolean linearize) { - //TODO once opglES3.0 is supported maybe.... - } - - public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { - throw new UnsupportedOperationException("Not supported yet. URA will make that work seamlessly"); - } -} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java deleted file mode 100644 index e614d1dfd..000000000 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ /dev/null @@ -1,2695 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.renderer.lwjgl; - -import com.jme3.light.LightList; -import com.jme3.material.RenderState; -import com.jme3.material.RenderState.StencilOperation; -import com.jme3.material.RenderState.TestFunction; -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.HashSet; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3tools.converters.MipMapGenerator; -import jme3tools.shader.ShaderDebug; - -import static org.lwjgl.opengl.ARBDrawInstanced.*; -import static org.lwjgl.opengl.ARBInstancedArrays.*; -import static org.lwjgl.opengl.ARBMultisample.*; -import static org.lwjgl.opengl.ARBTextureMultisample.*; -import static org.lwjgl.opengl.ARBVertexArrayObject.*; -import static org.lwjgl.opengl.EXTFramebufferBlit.*; -import static org.lwjgl.opengl.EXTFramebufferMultisample.*; -import static org.lwjgl.opengl.EXTFramebufferObject.*; -import static org.lwjgl.opengl.EXTFramebufferSRGB.*; -import static org.lwjgl.opengl.EXTTextureArray.*; -import static org.lwjgl.opengl.EXTTextureFilterAnisotropic.*; -import static org.lwjgl.opengl.GL11.*; -import static org.lwjgl.opengl.GL12.*; -import static org.lwjgl.opengl.GL13.*; -import static org.lwjgl.opengl.GL14.*; -import static org.lwjgl.opengl.GL15.*; -import static org.lwjgl.opengl.GL20.*; -import org.lwjgl.opengl.GL30; - -/** - * - * Should not be used, has been replaced by Unified Rendering Architechture. - * @deprecated - */ -@Deprecated -public class LwjglRenderer { - - private static final Logger logger = Logger.getLogger(LwjglRenderer.class.getName()); - private static final boolean VALIDATE_SHADER = false; - private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); - private final StringBuilder stringBuf = new StringBuilder(250); - private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); - private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); - private final FloatBuffer floatBuf16 = BufferUtils.createFloatBuffer(16); - private final RenderContext context = new RenderContext(); - private final NativeObjectManager objManager = new NativeObjectManager(); - private final EnumSet caps = EnumSet.noneOf(Caps.class); - - private int vertexTextureUnits; - private int fragTextureUnits; - private int vertexUniforms; - private int fragUniforms; - private int vertexAttribs; - private int maxFBOSamples; - private int maxFBOAttachs; - private int maxMRTFBOAttachs; - private int maxRBSize; - private int maxTexSize; - private int maxCubeTexSize; - private int maxVertCount; - private int maxTriCount; - private int maxColorTexSamples; - private int maxDepthTexSamples; - private FrameBuffer mainFbOverride = null; - private final Statistics statistics = new Statistics(); - private int vpX, vpY, vpW, vpH; - private int clipX, clipY, clipW, clipH; - private boolean linearizeSrgbImages; - private HashSet extensions; - - public LwjglRenderer() { - } - - 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(); - } - -// @Override - public Statistics getStatistics() { - return statistics; - } - - // @Override - public EnumSet getCaps() { - return caps; - } - - private static HashSet loadExtensions(String extensions) { - HashSet extensionSet = new HashSet(64); - for (String extension : extensions.split(" ")) { - extensionSet.add(extension); - } - return extensionSet; - } - - private static 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(); - } - - // Find a character which is not a period or digit. - for (int i = 0; i < versionStr.length(); i++) { - char c = versionStr.charAt(i); - if (c != '.' && (c < '0' || c > '9')) { - versionStr = versionStr.substring(0, i); - break; - } - } - - // Pivot on first point. - int firstPoint = versionStr.indexOf("."); - - // Remove everything after second point. - int secondPoint = versionStr.indexOf(".", firstPoint + 1); - - if (secondPoint != -1) { - versionStr = versionStr.substring(0, secondPoint); - } - - String majorVerStr = versionStr.substring(0, firstPoint); - String minorVerStr = versionStr.substring(firstPoint + 1); - - if (minorVerStr.endsWith("0") && minorVerStr.length() > 1) { - minorVerStr = minorVerStr.substring(0, minorVerStr.length() - 1); - } - - int majorVer = Integer.parseInt(majorVerStr); - int minorVer = Integer.parseInt(minorVerStr); - - return majorVer * 100 + minorVer * 10; - } else { - return -1; - } - } - - private boolean hasExtension(String extensionName) { - return extensions.contains(extensionName); - } - - private void loadCapabilities() { - int oglVer = extractVersion("", glGetString(GL_VERSION)); - - if (oglVer >= 200) { - caps.add(Caps.OpenGL20); - if (oglVer >= 210) { - caps.add(Caps.OpenGL21); - if (oglVer >= 300) { - caps.add(Caps.OpenGL30); - if (oglVer >= 310) { - caps.add(Caps.OpenGL31); - if (oglVer >= 320) { - caps.add(Caps.OpenGL32); - } - } - } - } - } - - int glslVer = extractVersion("", glGetString(GL_SHADING_LANGUAGE_VERSION)); - - switch (glslVer) { - default: - if (glslVer < 400) { - break; - } - // so that future OpenGL revisions wont break jme3 - // fall through intentional - case 400: - case 330: - case 150: - caps.add(Caps.GLSL150); - case 140: - caps.add(Caps.GLSL140); - case 130: - caps.add(Caps.GLSL130); - case 120: - caps.add(Caps.GLSL120); - case 110: - caps.add(Caps.GLSL110); - case 100: - caps.add(Caps.GLSL100); - break; - } - - // Workaround, always assume we support GLSL100. - // Some cards just don't report this correctly. - caps.add(Caps.GLSL100); - - extensions = loadExtensions(glGetString(GL_EXTENSIONS)); - } - - @SuppressWarnings("fallthrough") - public void initialize() { - loadCapabilities(); - - // Fix issue in TestRenderToMemory when GL_FRONT is the main - // buffer being used. - context.initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); - context.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; - - glGetInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); - vertexTextureUnits = intBuf16.get(0); - logger.log(Level.FINER, "VTF Units: {0}", vertexTextureUnits); - if (vertexTextureUnits > 0) { - caps.add(Caps.VertexTextureFetch); - } - - glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); - fragTextureUnits = intBuf16.get(0); - logger.log(Level.FINER, "Texture Units: {0}", fragTextureUnits); - - glGetInteger(GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16); - vertexUniforms = intBuf16.get(0); - logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); - - glGetInteger(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); - fragUniforms = intBuf16.get(0); - logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - - glGetInteger(GL_MAX_VERTEX_ATTRIBS, intBuf16); - vertexAttribs = intBuf16.get(0); - logger.log(Level.FINER, "Vertex Attributes: {0}", vertexAttribs); - - glGetInteger(GL_SUBPIXEL_BITS, intBuf16); - int subpixelBits = intBuf16.get(0); - logger.log(Level.FINER, "Subpixel Bits: {0}", subpixelBits); - - glGetInteger(GL_MAX_ELEMENTS_VERTICES, intBuf16); - maxVertCount = intBuf16.get(0); - logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); - - glGetInteger(GL_MAX_ELEMENTS_INDICES, intBuf16); - maxTriCount = intBuf16.get(0); - logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); - - glGetInteger(GL_MAX_TEXTURE_SIZE, intBuf16); - maxTexSize = intBuf16.get(0); - logger.log(Level.FINER, "Maximum Texture Resolution: {0}", maxTexSize); - - glGetInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); - maxCubeTexSize = intBuf16.get(0); - logger.log(Level.FINER, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); - - // ctxCaps = GLContext.getCapabilities(); - - if (hasExtension("GL_ARB_color_buffer_float") && - hasExtension("GL_ARB_half_float_pixel")) { - // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. - caps.add(Caps.FloatColorBuffer); - } - - if (hasExtension("GL_ARB_depth_buffer_float")) { - caps.add(Caps.FloatDepthBuffer); - } - - if (caps.contains(Caps.OpenGL30)) { - caps.add(Caps.PackedDepthStencilBuffer); - } - - if (hasExtension("GL_ARB_draw_instanced") && - hasExtension("GL_ARB_instanced_arrays")) { - caps.add(Caps.MeshInstancing); - } - - if (hasExtension("GL_ARB_texture_buffer_object")) { - caps.add(Caps.TextureBuffer); - } - - if (hasExtension("GL_ARB_texture_float") && - hasExtension("GL_ARB_half_float_pixel")) { - caps.add(Caps.FloatTexture); - } - - if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) { - caps.add(Caps.VertexBufferArray); - } - - if (hasExtension("GL_ARB_texture_non_power_of_two") || caps.contains(Caps.OpenGL30)) { - caps.add(Caps.NonPowerOfTwoTextures); - } else { - logger.log(Level.WARNING, "Your graphics card does not " - + "support non-power-of-2 textures. " - + "Some features might not work."); - } - - if (hasExtension("GL_EXT_texture_compression_s3tc")) { - caps.add(Caps.TextureCompressionS3TC); - } - - if (hasExtension("GL_ARB_ES3_compatibility")) { - caps.add(Caps.TextureCompressionETC1); - } - - if (hasExtension("GL_EXT_packed_float") || caps.contains(Caps.OpenGL30)) { - // This format is part of the OGL3 specification - caps.add(Caps.PackedFloatColorBuffer); - - if (hasExtension("GL_ARB_half_float_pixel")) { - // because textures are usually uploaded as RGB16F - // need half-float pixel - caps.add(Caps.PackedFloatTexture); - } - } - - if (hasExtension("GL_EXT_texture_array") || caps.contains(Caps.OpenGL30)) { - caps.add(Caps.TextureArray); - } - - if (hasExtension("GL_EXT_texture_shared_exponent") || caps.contains(Caps.OpenGL30)) { - caps.add(Caps.SharedExponentTexture); - } - - if (hasExtension("GL_EXT_texture_filter_anisotropic")) { - caps.add(Caps.TextureFilterAnisotropic); - } - - if (hasExtension("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 (hasExtension("GL_EXT_framebuffer_blit")) { - caps.add(Caps.FrameBufferBlit); - } - - if (hasExtension("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 (hasExtension("GL_ARB_texture_multisample")) { - caps.add(Caps.TextureMultisample); - - glGetInteger(GL_MAX_COLOR_TEXTURE_SAMPLES, intBuf16); - maxColorTexSamples = intBuf16.get(0); - logger.log(Level.FINER, "Texture Multisample Color Samples: {0}", maxColorTexSamples); - - glGetInteger(GL_MAX_DEPTH_TEXTURE_SAMPLES, intBuf16); - maxDepthTexSamples = intBuf16.get(0); - logger.log(Level.FINER, "Texture Multisample Depth Samples: {0}", maxDepthTexSamples); - } - - if (hasExtension("GL_ARB_draw_buffers")) { - glGetInteger(GL_MAX_DRAW_BUFFERS, intBuf16); - maxMRTFBOAttachs = intBuf16.get(0); - if (maxMRTFBOAttachs > 1) { - caps.add(Caps.FrameBufferMRT); - logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); - } - } - } - - if (hasExtension("GL_ARB_multisample")) { - glGetInteger(GL_SAMPLE_BUFFERS_ARB, intBuf16); - boolean available = intBuf16.get(0) != 0; - glGetInteger(GL_SAMPLES_ARB, intBuf16); - int samples = intBuf16.get(0); - logger.log(Level.FINER, "Samples: {0}", samples); - boolean enabled = glIsEnabled(GL_MULTISAMPLE_ARB); - if (samples > 0 && available && !enabled) { - glEnable(GL_MULTISAMPLE_ARB); - } - caps.add(Caps.Multisample); - } - - // Supports sRGB pipeline. - if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) - || caps.contains(Caps.OpenGL30) ) { - caps.add(Caps.Srgb); - } - - logger.log(Level.FINE, "Caps: {0}", caps); - } - - public void invalidateState() { - context.reset(); - context.initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); - context.initialReadBuf = glGetInteger(GL_READ_BUFFER); - } - - public void resetGLObjects() { - logger.log(Level.FINE, "Reseting objects and invalidating state"); - objManager.resetObjects(); - statistics.clearMemory(); - invalidateState(); - } - - public void cleanup() { - logger.log(Level.FINE, "Deleting objects and invalidating state"); - objManager.deleteAllObjects(this); - statistics.clearMemory(); - invalidateState(); - } - - private void checkCap(Caps cap) { - if (!caps.contains(cap)) { - throw new UnsupportedOperationException("Required capability missing: " + cap.name()); - } - } - - /*********************************************************************\ - |* Render State *| - \*********************************************************************/ - public void setDepthRange(float start, float end) { - glDepthRange(start, end); - } - - public void clearBuffers(boolean color, boolean depth, boolean stencil) { - 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) { - glColorMask(true, true, true, true); - context.colorWriteEnabled = true; - } - bits = 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) { - glDepthMask(true); - context.depthWriteEnabled = true; - } - bits |= GL_DEPTH_BUFFER_BIT; - } - if (stencil) { - bits |= GL_STENCIL_BUFFER_BIT; - } - if (bits != 0) { - glClear(bits); - } - } - - public void setBackgroundColor(ColorRGBA color) { - glClearColor(color.r, color.g, color.b, color.a); - } - - public void setAlphaToCoverage(boolean value) { - if (caps.contains(Caps.Multisample)) { - if (value) { - glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); - } else { - glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); - } - } - } - - public void applyRenderState(RenderState state) { - if (state.isWireframe() && !context.wireframe) { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - context.wireframe = true; - } else if (!state.isWireframe() && context.wireframe) { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - context.wireframe = false; - } - - if (state.isDepthTest() && !context.depthTestEnabled) { - glEnable(GL_DEPTH_TEST); - glDepthFunc(convertTestFunction(context.depthFunc)); - context.depthTestEnabled = true; - } else if (!state.isDepthTest() && context.depthTestEnabled) { - glDisable(GL_DEPTH_TEST); - context.depthTestEnabled = false; - } - if (state.getDepthFunc() != context.depthFunc) { - glDepthFunc(convertTestFunction(state.getDepthFunc())); - context.depthFunc = state.getDepthFunc(); - } - - if (state.isAlphaTest() && !context.alphaTestEnabled) { - glEnable(GL_ALPHA_TEST); - glAlphaFunc(convertTestFunction(context.alphaFunc), context.alphaTestFallOff); - context.alphaTestEnabled = true; - } else if (!state.isAlphaTest() && context.alphaTestEnabled) { - glDisable(GL_ALPHA_TEST); - context.alphaTestEnabled = false; - } - if (state.getAlphaFallOff() != context.alphaTestFallOff) { - glAlphaFunc(convertTestFunction(context.alphaFunc), context.alphaTestFallOff); - context.alphaTestFallOff = state.getAlphaFallOff(); - } - if (state.getAlphaFunc() != context.alphaFunc) { - glAlphaFunc(convertTestFunction(state.getAlphaFunc()), context.alphaTestFallOff); - context.alphaFunc = state.getAlphaFunc(); - } - - if (state.isDepthWrite() && !context.depthWriteEnabled) { - glDepthMask(true); - context.depthWriteEnabled = true; - } else if (!state.isDepthWrite() && context.depthWriteEnabled) { - glDepthMask(false); - context.depthWriteEnabled = false; - } - - if (state.isColorWrite() && !context.colorWriteEnabled) { - glColorMask(true, true, true, true); - context.colorWriteEnabled = true; - } else if (!state.isColorWrite() && context.colorWriteEnabled) { - glColorMask(false, false, false, false); - context.colorWriteEnabled = false; - } - - if (state.isPointSprite() && !context.pointSprite) { - // Only enable/disable sprite - if (context.boundTextures[0] != null) { - if (context.boundTextureUnit != 0) { - glActiveTexture(GL_TEXTURE0); - context.boundTextureUnit = 0; - } - glEnable(GL_POINT_SPRITE); - glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); - } - context.pointSprite = true; - } else if (!state.isPointSprite() && context.pointSprite) { - if (context.boundTextures[0] != null) { - if (context.boundTextureUnit != 0) { - glActiveTexture(GL_TEXTURE0); - context.boundTextureUnit = 0; - } - glDisable(GL_POINT_SPRITE); - glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); - context.pointSprite = false; - } - } - - if (state.isPolyOffset()) { - if (!context.polyOffsetEnabled) { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - context.polyOffsetEnabled = true; - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } else { - if (state.getPolyOffsetFactor() != context.polyOffsetFactor - || state.getPolyOffsetUnits() != context.polyOffsetUnits) { - glPolygonOffset(state.getPolyOffsetFactor(), - state.getPolyOffsetUnits()); - context.polyOffsetFactor = state.getPolyOffsetFactor(); - context.polyOffsetUnits = state.getPolyOffsetUnits(); - } - } - } else { - if (context.polyOffsetEnabled) { - glDisable(GL_POLYGON_OFFSET_FILL); - context.polyOffsetEnabled = false; - context.polyOffsetFactor = 0; - context.polyOffsetUnits = 0; - } - } - - if (state.getFaceCullMode() != context.cullMode) { - if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { - glDisable(GL_CULL_FACE); - } else { - glEnable(GL_CULL_FACE); - } - - switch (state.getFaceCullMode()) { - case Off: - break; - case Back: - glCullFace(GL_BACK); - break; - case Front: - glCullFace(GL_FRONT); - break; - case FrontAndBack: - glCullFace(GL_FRONT_AND_BACK); - 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) { - glDisable(GL_BLEND); - } else { - glEnable(GL_BLEND); - switch (state.getBlendMode()) { - case Off: - break; - case Additive: - glBlendFunc(GL_ONE, GL_ONE); - break; - case AlphaAdditive: - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case Color: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - break; - case Alpha: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case PremultAlpha: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - break; - case Modulate: - glBlendFunc(GL_DST_COLOR, GL_ZERO); - break; - case ModulateX2: - glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); - break; - case Screen: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - break; - case Exclusion: - glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR); - break; - default: - throw new UnsupportedOperationException("Unrecognized blend mode: " - + state.getBlendMode()); - } - } - - context.blendMode = state.getBlendMode(); - } - - if (context.stencilTest != state.isStencilTest() - || context.frontStencilStencilFailOperation != state.getFrontStencilStencilFailOperation() - || context.frontStencilDepthFailOperation != state.getFrontStencilDepthFailOperation() - || context.frontStencilDepthPassOperation != state.getFrontStencilDepthPassOperation() - || context.backStencilStencilFailOperation != state.getBackStencilStencilFailOperation() - || context.backStencilDepthFailOperation != state.getBackStencilDepthFailOperation() - || context.backStencilDepthPassOperation != state.getBackStencilDepthPassOperation() - || context.frontStencilFunction != state.getFrontStencilFunction() - || context.backStencilFunction != state.getBackStencilFunction()) { - - context.frontStencilStencilFailOperation = state.getFrontStencilStencilFailOperation(); //terrible looking, I know - context.frontStencilDepthFailOperation = state.getFrontStencilDepthFailOperation(); - context.frontStencilDepthPassOperation = state.getFrontStencilDepthPassOperation(); - context.backStencilStencilFailOperation = state.getBackStencilStencilFailOperation(); - context.backStencilDepthFailOperation = state.getBackStencilDepthFailOperation(); - context.backStencilDepthPassOperation = state.getBackStencilDepthPassOperation(); - context.frontStencilFunction = state.getFrontStencilFunction(); - context.backStencilFunction = state.getBackStencilFunction(); - - if (state.isStencilTest()) { - glEnable(GL_STENCIL_TEST); - glStencilOpSeparate(GL_FRONT, - convertStencilOperation(state.getFrontStencilStencilFailOperation()), - convertStencilOperation(state.getFrontStencilDepthFailOperation()), - convertStencilOperation(state.getFrontStencilDepthPassOperation())); - glStencilOpSeparate(GL_BACK, - convertStencilOperation(state.getBackStencilStencilFailOperation()), - convertStencilOperation(state.getBackStencilDepthFailOperation()), - convertStencilOperation(state.getBackStencilDepthPassOperation())); - glStencilFuncSeparate(GL_FRONT, - convertTestFunction(state.getFrontStencilFunction()), - 0, Integer.MAX_VALUE); - glStencilFuncSeparate(GL_BACK, - convertTestFunction(state.getBackStencilFunction()), - 0, Integer.MAX_VALUE); - } else { - glDisable(GL_STENCIL_TEST); - } - } - } - - private int convertStencilOperation(StencilOperation stencilOp) { - switch (stencilOp) { - case Keep: - return GL_KEEP; - case Zero: - return GL_ZERO; - case Replace: - return GL_REPLACE; - case Increment: - return GL_INCR; - case IncrementWrap: - return GL_INCR_WRAP; - case Decrement: - return GL_DECR; - case DecrementWrap: - return GL_DECR_WRAP; - case Invert: - return GL_INVERT; - default: - throw new UnsupportedOperationException("Unrecognized stencil operation: " + stencilOp); - } - } - - private int convertTestFunction(TestFunction testFunc) { - switch (testFunc) { - case Never: - return GL_NEVER; - case Less: - return GL_LESS; - case LessOrEqual: - return GL_LEQUAL; - case Greater: - return GL_GREATER; - case GreaterOrEqual: - return GL_GEQUAL; - case Equal: - return GL_EQUAL; - case NotEqual: - return GL_NOTEQUAL; - case Always: - return GL_ALWAYS; - default: - throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); - } - } - - /*********************************************************************\ - |* Camera and World transforms *| - \*********************************************************************/ - public void setViewPort(int x, int y, int w, int h) { - if (x != vpX || vpY != y || vpW != w || vpH != h) { - glViewport(x, y, w, h); - vpX = x; - vpY = y; - vpW = w; - vpH = h; - } - } - - public void setClipRect(int x, int y, int width, int height) { - if (!context.clipRectEnabled) { - glEnable(GL_SCISSOR_TEST); - context.clipRectEnabled = true; - } - if (clipX != x || clipY != y || clipW != width || clipH != height) { - glScissor(x, y, width, height); - clipX = x; - clipY = y; - clipW = width; - clipH = height; - } - } - - public void clearClipRect() { - if (context.clipRectEnabled) { - glDisable(GL_SCISSOR_TEST); - context.clipRectEnabled = false; - - clipX = 0; - clipY = 0; - clipW = 0; - clipH = 0; - } - } - - public void postFrame() { - objManager.deleteUnused(this); -// statistics.clearFrame(); - } - - public void setWorldMatrix(Matrix4f worldMatrix) { - } - - public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { - } - - /*********************************************************************\ - |* Shaders *| - \*********************************************************************/ - protected void updateUniformLocation(Shader shader, Uniform uniform) { - stringBuf.setLength(0); - stringBuf.append(uniform.getName()).append('\0'); - updateNameBuffer(); - int loc = glGetUniformLocation(shader.getId(), nameBuf); - if (loc < 0) { - uniform.setLocation(-1); - // uniform is not declared in shader - logger.log(Level.FINE, "Uniform {0} is not declared in shader {1}.", new Object[]{uniform.getName(), shader.getSources()}); - } else { - uniform.setLocation(loc); - } - } - - protected void bindProgram(Shader shader) { - int shaderId = shader.getId(); - if (context.boundShaderProgram != shaderId) { - glUseProgram(shaderId); - statistics.onShaderUse(shader, true); - context.boundShader = shader; - context.boundShaderProgram = shaderId; - } else { - statistics.onShaderUse(shader, false); - } - } - - protected void updateUniform(Shader shader, Uniform uniform) { - int shaderId = shader.getId(); - - assert uniform.getName() != null; - assert shader.getId() > 0; - - bindProgram(shader); - - 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) { - return; // value not set yet.. - } - statistics.onUniformSet(); - - uniform.clearUpdateNeeded(); - FloatBuffer fb; - IntBuffer ib; - switch (uniform.getVarType()) { - case Float: - Float f = (Float) uniform.getValue(); - glUniform1f(loc, f.floatValue()); - break; - case Vector2: - Vector2f v2 = (Vector2f) uniform.getValue(); - glUniform2f(loc, v2.getX(), v2.getY()); - break; - case Vector3: - Vector3f v3 = (Vector3f) uniform.getValue(); - glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); - break; - case Vector4: - Object val = uniform.getValue(); - if (val instanceof ColorRGBA) { - ColorRGBA c = (ColorRGBA) val; - glUniform4f(loc, c.r, c.g, c.b, c.a); - } else if (val instanceof Vector4f) { - Vector4f c = (Vector4f) val; - glUniform4f(loc, c.x, c.y, c.z, c.w); - } else { - Quaternion c = (Quaternion) uniform.getValue(); - glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); - } - break; - case Boolean: - Boolean b = (Boolean) uniform.getValue(); - glUniform1i(loc, b.booleanValue() ? GL_TRUE : GL_FALSE); - break; - case Matrix3: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 9; - glUniformMatrix3(loc, false, fb); - break; - case Matrix4: - fb = (FloatBuffer) uniform.getValue(); - assert fb.remaining() == 16; - glUniformMatrix4(loc, false, fb); - break; - case IntArray: - ib = (IntBuffer) uniform.getValue(); - glUniform1(loc, ib); - break; - case FloatArray: - fb = (FloatBuffer) uniform.getValue(); - glUniform1(loc, fb); - break; - case Vector2Array: - fb = (FloatBuffer) uniform.getValue(); - glUniform2(loc, fb); - break; - case Vector3Array: - fb = (FloatBuffer) uniform.getValue(); - glUniform3(loc, fb); - break; - case Vector4Array: - fb = (FloatBuffer) uniform.getValue(); - glUniform4(loc, fb); - break; - case Matrix4Array: - fb = (FloatBuffer) uniform.getValue(); - glUniformMatrix4(loc, false, fb); - break; - case Int: - Integer i = (Integer) uniform.getValue(); - glUniform1i(loc, i.intValue()); - break; - default: - throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); - } - } - - 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 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 - } - } - - /* - * (Non-javadoc) - * Only used for fixed-function. Ignored. - */ - public void setLighting(LightList list) { - } - - public int convertShaderType(ShaderType type) { - switch (type) { - case Fragment: - return GL_FRAGMENT_SHADER; - case Vertex: - return GL_VERTEX_SHADER; - default: - throw new UnsupportedOperationException("Unrecognized shader type."); - } - } - - public void updateShaderSourceData(ShaderSource source) { - int id = source.getId(); - if (id == -1) { - // Create id - id = glCreateShader(convertShaderType(source.getType())); - if (id <= 0) { - throw new RendererException("Invalid ID received when trying to create shader."); - } - - source.setId(id); - } else { - throw new RendererException("Cannot recompile shader source"); - } - - // Upload shader source. - // Merge the defines and source code. - String language = source.getLanguage(); - stringBuf.setLength(0); - if (language.startsWith("GLSL")) { - int version = Integer.parseInt(language.substring(4)); - if (version > 100) { - stringBuf.append("#version "); - stringBuf.append(language.substring(4)); - if (version >= 150) { - stringBuf.append(" core"); - } - stringBuf.append("\n"); - } else { - // version 100 does not exist in desktop GLSL. - // put version 110 in that case to enable strict checking - stringBuf.append("#version 110\n"); - } - } - updateNameBuffer(); - - byte[] definesCodeData = source.getDefines().getBytes(); - byte[] sourceCodeData = source.getSource().getBytes(); - ByteBuffer codeBuf = BufferUtils.createByteBuffer(nameBuf.limit() - + definesCodeData.length - + sourceCodeData.length); - codeBuf.put(nameBuf); - codeBuf.put(definesCodeData); - codeBuf.put(sourceCodeData); - codeBuf.flip(); - - glShaderSource(id, codeBuf); - glCompileShader(id); - - glGetShader(id, GL_COMPILE_STATUS, intBuf1); - - boolean compiledOK = intBuf1.get(0) == GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !compiledOK) { - // even if compile succeeded, check - // log for warnings - glGetShader(id, GL_INFO_LOG_LENGTH, intBuf1); - int length = intBuf1.get(0); - if (length > 3) { - // get infos - ByteBuffer logBuf = BufferUtils.createByteBuffer(length); - glGetShaderInfoLog(id, null, logBuf); - byte[] logBytes = new byte[length]; - logBuf.get(logBytes, 0, length); - // convert to string, etc - infoLog = new String(logBytes); - } - } - - if (compiledOK) { - if (infoLog != null) { - logger.log(Level.WARNING, "{0} compiled successfully, compiler warnings: \n{1}", - new Object[]{source.getName(), infoLog}); - } else { - logger.log(Level.FINE, "{0} compiled successfully.", 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 void updateShaderData(Shader shader) { - int id = shader.getId(); - boolean needRegister = false; - if (id == -1) { - // create program - id = glCreateProgram(); - if (id == 0) { - throw new RendererException("Invalid ID (" + id + ") received when trying to create shader program."); - } - - shader.setId(id); - needRegister = true; - } - - for (ShaderSource source : shader.getSources()) { - if (source.isUpdateNeeded()) { - updateShaderSourceData(source); - } - glAttachShader(id, source.getId()); - } - - if (caps.contains(Caps.OpenGL30)) { - // Check if GLSL version is 1.5 for shader - GL30.glBindFragDataLocation(id, 0, "outFragColor"); - // For MRT - for (int i = 0; i < maxMRTFBOAttachs; i++) { - GL30.glBindFragDataLocation(id, i, "outFragData[" + i + "]"); - } - } - - // Link shaders to program - glLinkProgram(id); - - // Check link status - glGetProgram(id, GL_LINK_STATUS, intBuf1); - boolean linkOK = intBuf1.get(0) == GL_TRUE; - String infoLog = null; - - if (VALIDATE_SHADER || !linkOK) { - glGetProgram(id, GL_INFO_LOG_LENGTH, intBuf1); - int length = intBuf1.get(0); - if (length > 3) { - // get infos - ByteBuffer logBuf = BufferUtils.createByteBuffer(length); - glGetProgramInfoLog(id, null, logBuf); - - // convert to string, etc - byte[] logBytes = new byte[length]; - logBuf.get(logBytes, 0, length); - infoLog = new String(logBytes); - } - } - - if (linkOK) { - if (infoLog != null) { - logger.log(Level.WARNING, "Shader linked successfully. Linker warnings: \n{0}", infoLog); - } else { - logger.fine("Shader linked successfully."); - } - 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 failed to link, shader:" + shader + "\n" + infoLog); - } else { - throw new RendererException("Shader failed to link, shader:" + shader + "\ninfo: "); - } - } - } - - public void setShader(Shader shader) { - 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); - } - } - - public void deleteShaderSource(ShaderSource source) { - if (source.getId() < 0) { - logger.warning("Shader source is not uploaded to GPU, cannot delete."); - return; - } - source.clearUpdateNeeded(); - glDeleteShader(source.getId()); - source.resetObject(); - } - - public void deleteShader(Shader shader) { - if (shader.getId() == -1) { - logger.warning("Shader is not uploaded to GPU, cannot delete."); - return; - } - - for (ShaderSource source : shader.getSources()) { - if (source.getId() != -1) { - glDetachShader(shader.getId(), source.getId()); - deleteShaderSource(source); - } - } - - glDeleteProgram(shader.getId()); - statistics.onDeleteShader(); - shader.resetObject(); - } - - /*********************************************************************\ - |* Framebuffers *| - \*********************************************************************/ - public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { - copyFrameBuffer(src, dst, true); - } - - public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { - if (caps.contains(Caps.FrameBufferBlit)) { - int srcX0 = 0; - int srcY0 = 0; - int srcX1; - int srcY1; - - int dstX0 = 0; - int dstY0 = 0; - int dstX1; - int dstY1; - - int prevFBO = context.boundFBO; - - if (mainFbOverride != null) { - if (src == null) { - src = mainFbOverride; - } - if (dst == null) { - dst = mainFbOverride; - } - } - - if (src != null && src.isUpdateNeeded()) { - updateFrameBuffer(src); - } - - if (dst != null && dst.isUpdateNeeded()) { - updateFrameBuffer(dst); - } - - if (src == null) { - glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); - srcX0 = vpX; - srcY0 = vpY; - srcX1 = vpX + vpW; - srcY1 = vpY + vpH; - } else { - glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src.getId()); - srcX1 = src.getWidth(); - srcY1 = src.getHeight(); - } - if (dst == null) { - glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); - dstX0 = vpX; - dstY0 = vpY; - dstX1 = vpX + vpW; - dstY1 = vpY + vpH; - } else { - glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); - dstX1 = dst.getWidth(); - dstY1 = dst.getHeight(); - } - int mask = GL_COLOR_BUFFER_BIT; - if (copyDepth) { - mask |= GL_DEPTH_BUFFER_BIT; - } - glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, - dstX0, dstY0, dstX1, dstY1, mask, - GL_NEAREST); - - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, prevFBO); - try { - checkFrameBufferError(); - } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, "Source FBO:\n{0}", src); - logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); - throw ex; - } - } else { - throw new RendererException("EXT_framebuffer_blit required."); - // TODO: support non-blit copies? - } - } - - private String getTargetBufferName(int buffer) { - switch (buffer) { - case GL_NONE: - return "NONE"; - case GL_FRONT: - return "GL_FRONT"; - case GL_BACK: - return "GL_BACK"; - default: - if (buffer >= GL_COLOR_ATTACHMENT0_EXT - && buffer <= GL_COLOR_ATTACHMENT15_EXT) { - return "GL_COLOR_ATTACHMENT" - + (buffer - GL_COLOR_ATTACHMENT0_EXT); - } else { - return "UNKNOWN? " + buffer; - } - } - } - - 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? " + glIsRenderbufferEXT(rb.getId())); - - int attachment = convertAttachmentSlot(rb.getSlot()); - - int type = glGetFramebufferAttachmentParameteriEXT(GL_DRAW_FRAMEBUFFER_EXT, - attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT); - - int rbName = glGetFramebufferAttachmentParameteriEXT(GL_DRAW_FRAMEBUFFER_EXT, - attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT); - - switch (type) { - case GL_NONE: - System.out.println("Type: None"); - break; - case GL_TEXTURE: - System.out.println("Type: Texture"); - break; - case GL_RENDERBUFFER_EXT: - System.out.println("Type: Buffer"); - System.out.println("RB ID: " + rbName); - break; - } - - - - } - - private void printRealFrameBufferInfo(FrameBuffer fb) { - boolean doubleBuffer = glGetBoolean(GL_DOUBLEBUFFER); - String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER)); - String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER)); - - int fbId = fb.getId(); - int curDrawBinding = glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING_EXT); - int curReadBinding = glGetInteger(GL_READ_FRAMEBUFFER_BINDING_EXT); - - System.out.println("=== OpenGL FBO State ==="); - System.out.println("Context doublebuffered? " + doubleBuffer); - System.out.println("FBO ID: " + fbId); - System.out.println("Is proper? " + glIsFramebufferEXT(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) { - glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 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); - } - } - - private void checkFrameBufferError() { - int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); - switch (status) { - case GL_FRAMEBUFFER_COMPLETE_EXT: - break; - case GL_FRAMEBUFFER_UNSUPPORTED_EXT: - //Choose different formats - throw new IllegalStateException("Framebuffer object format is " - + "unsupported by the video hardware."); - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: - throw new IllegalStateException("Framebuffer has erronous attachment."); - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: - throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: - throw new IllegalStateException("Framebuffer attachments must have same dimensions."); - case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: - throw new IllegalStateException("Framebuffer attachments must have same formats."); - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: - throw new IllegalStateException("Incomplete draw buffer."); - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: - throw new IllegalStateException("Incomplete read buffer."); - case 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. "); - } - } - - private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - int id = rb.getId(); - if (id == -1) { - glGenRenderbuffersEXT(intBuf1); - id = intBuf1.get(0); - rb.setId(id); - } - - if (context.boundRB != id) { - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id); - context.boundRB = id; - } - - if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { - throw new RendererException("Resolution " + fb.getWidth() - + ":" + fb.getHeight() + " is not supported."); - } - - TextureUtil.GLImageFormat glFmt = TextureUtil.getImageFormatWithError(caps, rb.getFormat(), fb.isSrgb()); - - if (fb.getSamples() > 1 && caps.contains(Caps.FrameBufferMultisample)) { - int samples = fb.getSamples(); - if (maxFBOSamples < samples) { - samples = maxFBOSamples; - } - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, - samples, - glFmt.internalFormat, - fb.getWidth(), - fb.getHeight()); - } else { - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, - glFmt.internalFormat, - fb.getWidth(), - fb.getHeight()); - } - } - - private int convertAttachmentSlot(int attachmentSlot) { - // can also add support for stencil here - if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { - return 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. - return GL30.GL_DEPTH_STENCIL_ATTACHMENT; - } else if (attachmentSlot < 0 || attachmentSlot >= 16) { - throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); - } - - return GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; - } - - public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { - Texture tex = rb.getTexture(); - Image image = tex.getImage(); - if (image.isUpdateNeeded()) { - updateTexImageData(image, tex.getType(), 0); - - // NOTE: For depth textures, sets nearest/no-mips mode - // Required to fix "framebuffer unsupported" - // for old NVIDIA drivers! - setupTextureParams(tex); - } - - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, - convertAttachmentSlot(rb.getSlot()), - convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), - image.getId(), - 0); - } - - 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) { - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, - convertAttachmentSlot(rb.getSlot()), - GL_RENDERBUFFER_EXT, - rb.getId()); - } - } - - public void updateFrameBuffer(FrameBuffer fb) { - int id = fb.getId(); - if (id == -1) { - // create FBO - glGenFramebuffersEXT(intBuf1); - id = intBuf1.get(0); - fb.setId(id); - objManager.registerObject(fb); - - statistics.onNewFrameBuffer(); - } - - if (context.boundFBO != id) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id); - // 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 Vector2f[] getFrameBufferSamplePositions(FrameBuffer fb) { - if (fb.getSamples() <= 1) { - throw new IllegalArgumentException("Framebuffer must be multisampled"); - } - if (!caps.contains(Caps.TextureMultisample)) { - throw new RendererException("Multisampled textures are not supported"); - } - - setFrameBuffer(fb); - - Vector2f[] samplePositions = new Vector2f[fb.getSamples()]; - FloatBuffer samplePos = BufferUtils.createFloatBuffer(2); - for (int i = 0; i < samplePositions.length; i++) { - glGetMultisample(GL_SAMPLE_POSITION, i, samplePos); - samplePos.clear(); - samplePositions[i] = new Vector2f(samplePos.get(0) - 0.5f, - samplePos.get(1) - 0.5f); - } - return samplePositions; - } - - public void setMainFrameBufferOverride(FrameBuffer fb) { - mainFbOverride = fb; - } - - public void setFrameBuffer(FrameBuffer fb) { - if (!caps.contains(Caps.FrameBuffer)) { - throw new RendererException("Framebuffer objects are not supported" + - " by the video hardware"); - } - - if (fb == null && mainFbOverride != null) { - fb = mainFbOverride; - } - - if (context.boundFB == fb) { - if (fb == null || !fb.isUpdateNeeded()) { - return; - } - } - - // generate mipmaps for last FB if needed - if (context.boundFB != null) { - for (int i = 0; i < context.boundFB.getNumColorBuffers(); i++) { - RenderBuffer rb = context.boundFB.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()); - glEnable(textureType); - glGenerateMipmapEXT(textureType); - glDisable(textureType); - } - } - } - - if (fb == null) { - // unbind any fbos - if (context.boundFBO != 0) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - statistics.onFrameBufferUse(null, true); - - context.boundFBO = 0; - } - // select back buffer - if (context.boundDrawBuf != -1) { - glDrawBuffer(context.initialDrawBuf); - context.boundDrawBuf = -1; - } - if (context.boundReadBuf != -1) { - glReadBuffer(context.initialReadBuf); - context.boundReadBuf = -1; - } - - context.boundFB = 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()) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb.getId()); - 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 < fb.getNumColorBuffers(); i++) { - intBuf16.put(GL_COLOR_ATTACHMENT0_EXT + i); - } - - intBuf16.flip(); - glDrawBuffers(intBuf16); - context.boundDrawBuf = 100 + fb.getNumColorBuffers(); - } - } else { - RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); - // select this draw buffer - if (context.boundDrawBuf != rb.getSlot()) { - glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); - context.boundDrawBuf = rb.getSlot(); - } - } - } - - assert fb.getId() >= 0; - assert context.boundFBO == fb.getId(); - - context.boundFB = fb; - - try { - checkFrameBufferError(); - } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb); - printRealFrameBufferInfo(fb); - throw ex; - } - } - } - - public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { - if (fb != null) { - RenderBuffer rb = fb.getColorBuffer(); - if (rb == null) { - throw new IllegalArgumentException("Specified framebuffer" - + " does not have a colorbuffer"); - } - - setFrameBuffer(fb); - if (context.boundReadBuf != rb.getSlot()) { - glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); - context.boundReadBuf = rb.getSlot(); - } - } else { - setFrameBuffer(null); - } - - glReadPixels(vpX, vpY, vpW, vpH, /*GL_RGBA*/ GL_BGRA, GL_UNSIGNED_BYTE, byteBuf); - } - - private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { - intBuf1.put(0, rb.getId()); - glDeleteRenderbuffersEXT(intBuf1); - } - - public void deleteFrameBuffer(FrameBuffer fb) { - if (fb.getId() != -1) { - if (context.boundFBO == fb.getId()) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - context.boundFBO = 0; - } - - if (fb.getDepthBuffer() != null) { - deleteRenderBuffer(fb, fb.getDepthBuffer()); - } - if (fb.getColorBuffer() != null) { - deleteRenderBuffer(fb, fb.getColorBuffer()); - } - - intBuf1.put(0, fb.getId()); - glDeleteFramebuffersEXT(intBuf1); - fb.resetObject(); - - statistics.onDeleteFrameBuffer(); - } - } - - /*********************************************************************\ - |* Textures *| - \*********************************************************************/ - private int convertTextureType(Texture.Type type, int samples, int face) { - if (samples > 1 && !caps.contains(Caps.TextureMultisample)) { - throw new RendererException("Multisample textures are not supported" + - " by the video hardware."); - } - - switch (type) { - case TwoDimensional: - if (samples > 1) { - return GL_TEXTURE_2D_MULTISAMPLE; - } else { - return GL_TEXTURE_2D; - } - case TwoDimensionalArray: - if (samples > 1) { - return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; - } else { - return GL_TEXTURE_2D_ARRAY_EXT; - } - case ThreeDimensional: - return GL_TEXTURE_3D; - case CubeMap: - if (face < 0) { - return GL_TEXTURE_CUBE_MAP; - } else if (face < 6) { - return GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; - } else { - throw new UnsupportedOperationException("Invalid cube map face index: " + face); - } - default: - throw new UnsupportedOperationException("Unknown texture type: " + type); - } - } - - private int convertMagFilter(Texture.MagFilter filter) { - switch (filter) { - case Bilinear: - return GL_LINEAR; - case Nearest: - return GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown mag filter: " + filter); - } - } - - private int convertMinFilter(Texture.MinFilter filter, boolean haveMips) { - if (haveMips){ - switch (filter) { - case Trilinear: - return GL_LINEAR_MIPMAP_LINEAR; - case BilinearNearestMipMap: - return GL_LINEAR_MIPMAP_NEAREST; - case NearestLinearMipMap: - return GL_NEAREST_MIPMAP_LINEAR; - case NearestNearestMipMap: - return GL_NEAREST_MIPMAP_NEAREST; - case BilinearNoMipMaps: - return GL_LINEAR; - case NearestNoMipMaps: - return GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown min filter: " + filter); - } - } else { - switch (filter) { - case Trilinear: - case BilinearNearestMipMap: - case BilinearNoMipMaps: - return GL_LINEAR; - case NearestLinearMipMap: - case NearestNearestMipMap: - case NearestNoMipMaps: - return GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown min filter: " + filter); - } - } - } - - private int convertWrapMode(Texture.WrapMode mode) { - switch (mode) { - case BorderClamp: - return GL_CLAMP_TO_BORDER; - case Clamp: - // Falldown intentional. - case EdgeClamp: - return GL_CLAMP_TO_EDGE; - case Repeat: - return GL_REPEAT; - case MirroredRepeat: - return GL_MIRRORED_REPEAT; - default: - throw new UnsupportedOperationException("Unknown wrap mode: " + mode); - } - } - - @SuppressWarnings("fallthrough") - private void setupTextureParams(Texture tex) { - Image image = tex.getImage(); - int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); - - boolean haveMips = true; - - if (image != null) { - haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps(); - } - - // filter things - int minFilter = convertMinFilter(tex.getMinFilter(), haveMips); - int magFilter = convertMagFilter(tex.getMagFilter()); - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter); - glTexParameteri(target, GL_TEXTURE_MAG_FILTER, magFilter); - - if (tex.getAnisotropicFilter() > 1) { - if (caps.contains(Caps.TextureFilterAnisotropic)) { - glTexParameterf(target, - GL_TEXTURE_MAX_ANISOTROPY_EXT, - tex.getAnisotropicFilter()); - } - } - - if (context.pointSprite) { - return; // Attempt to fix glTexParameter crash for some ATI GPUs - } - - // repeat modes - switch (tex.getType()) { - case ThreeDimensional: - case CubeMap: // cubemaps use 3D coords - glTexParameteri(target, GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); - //There is no break statement on purpose here - case TwoDimensional: - case TwoDimensionalArray: - glTexParameteri(target, GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); - // fall down here is intentional.. -// case OneDimensional: - glTexParameteri(target, GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); - break; - default: - throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); - } - - if(tex.isNeedCompareModeUpdate()){ - // R to Texture compare mode - if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) { - glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(target, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); - if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) { - glTexParameteri(target, GL_TEXTURE_COMPARE_FUNC, GL_GEQUAL); - } else { - glTexParameteri(target, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - } - }else{ - //restoring default value - glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } - tex.compareModeUpdated(); - } - } - - /** - * Uploads the given image to the GL driver. - * - * @param img The image to upload - * @param type How the data in the image argument should be interpreted. - * @param unit The texture slot to be used to upload the image, not important - */ - public void updateTexImageData(Image img, Texture.Type type, int unit) { - int texId = img.getId(); - if (texId == -1) { - // create texture - glGenTextures(intBuf1); - texId = intBuf1.get(0); - img.setId(texId); - objManager.registerObject(img); - - statistics.onNewTexture(); - } - - // bind texture - int target = convertTextureType(type, img.getMultiSamples(), -1); - if (context.boundTextureUnit != unit) { - glActiveTexture(GL_TEXTURE0 + unit); - context.boundTextureUnit = unit; - } - if (context.boundTextures[unit] != img) { - glBindTexture(target, texId); - context.boundTextures[unit] = img; - - statistics.onTextureUse(img, true); - } - - if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) { - // Image does not have mipmaps, but they are required. - // Generate from base level. - - if (!caps.contains(Caps.OpenGL30) && !caps.contains(Caps.OpenGLES20)) { - glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE); - img.setMipmapsGenerated(true); - } else { - // For OpenGL3 and up. - // We'll generate mipmaps via glGenerateMipmapEXT (see below) - } - } else if (img.hasMipmaps()) { - // Image already has mipmaps, set the max level based on the - // number of mipmaps we have. - glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1); - } else { - // Image does not have mipmaps and they are not required. - // Specify that that the texture has no mipmaps. - glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 0); - } - - int imageSamples = img.getMultiSamples(); - if (imageSamples > 1) { - if (img.getFormat().isDepthFormat()) { - img.setMultiSamples(Math.min(maxDepthTexSamples, imageSamples)); - } else { - img.setMultiSamples(Math.min(maxColorTexSamples, imageSamples)); - } - } - - // Yes, some OpenGL2 cards (GeForce 5) still dont support NPOT. - if (!caps.contains(Caps.NonPowerOfTwoTextures) && img.isNPOT()) { - if (img.getData(0) == null) { - throw new RendererException("non-power-of-2 framebuffer textures are not supported by the video hardware"); - } else { - MipMapGenerator.resizeToPowerOf2(img); - } - } - - // Check if graphics card doesn't support multisample textures - if (!caps.contains(Caps.TextureMultisample)) { - if (img.getMultiSamples() > 1) { - throw new RendererException("Multisample textures not supported by graphics hardware"); - } - } - - if (target == 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); - } - if (img.getWidth() != img.getHeight()) { - throw new RendererException("Cubemaps must have square dimensions"); - } - } else { - if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { - throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); - } - } - - if (target == GL_TEXTURE_CUBE_MAP) { - List data = img.getData(); - if (data.size() != 6) { - logger.log(Level.WARNING, "Invalid texture: {0}\n" - + "Cubemap textures must contain 6 data units.", img); - return; - } - for (int i = 0; i < 6; i++) { - TextureUtil.uploadTexture(caps, img, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, linearizeSrgbImages); - } - } else if (target == GL_TEXTURE_2D_ARRAY_EXT) { - if (!caps.contains(Caps.TextureArray)) { - throw new RendererException("Texture arrays not supported by graphics hardware"); - } - - List data = img.getData(); - - // -1 index specifies prepare data for 2D Array - TextureUtil.uploadTexture(caps, img, target, -1, 0, linearizeSrgbImages); - - for (int i = 0; i < data.size(); i++) { - // upload each slice of 2D array in turn - // this time with the appropriate index - TextureUtil.uploadTexture(caps, img, target, i, 0, linearizeSrgbImages); - } - } else { - TextureUtil.uploadTexture(caps, img, target, 0, 0, linearizeSrgbImages); - } - - if (img.getMultiSamples() != imageSamples) { - img.setMultiSamples(imageSamples); - } - - if (caps.contains(Caps.OpenGL30)) { - if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired() && img.getData() != null) { - // XXX: Required for ATI - glEnable(target); - glGenerateMipmapEXT(target); - glDisable(target); - img.setMipmapsGenerated(true); - } - } - - img.clearUpdateNeeded(); - } - - public void setTexture(int unit, Texture tex) { - Image image = tex.getImage(); - if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { - updateTexImageData(image, tex.getType(), unit); - } - - int texId = image.getId(); - assert texId != -1; - - Image[] textures = context.boundTextures; - - int type = convertTextureType(tex.getType(), image.getMultiSamples(), -1); -// if (!context.textureIndexList.moveToNew(unit)) { -// if (context.boundTextureUnit != unit){ -// glActiveTexture(GL_TEXTURE0 + unit); -// context.boundTextureUnit = unit; -// } -// glEnable(type); -// } - - if (context.boundTextureUnit != unit) { - glActiveTexture(GL_TEXTURE0 + unit); - context.boundTextureUnit = unit; - } - if (textures[unit] != image) { - glBindTexture(type, texId); - textures[unit] = image; - - statistics.onTextureUse(image, true); - } else { - statistics.onTextureUse(image, false); - } - - setupTextureParams(tex); - } - - public void modifyTexture(Texture tex, Image pixels, int x, int y) { - setTexture(0, tex); - TextureUtil.uploadSubTexture(caps, pixels, convertTextureType(tex.getType(), pixels.getMultiSamples(), -1), 0, x, y, linearizeSrgbImages); - } - - public void clearTextureUnits() { -// IDList textureList = context.textureIndexList; -// Image[] textures = context.boundTextures; -// for (int i = 0; i < textureList.oldLen; i++) { -// int idx = textureList.oldList[i]; -// if (context.boundTextureUnit != idx){ -// glActiveTexture(GL_TEXTURE0 + idx); -// context.boundTextureUnit = idx; -// } -// glDisable(convertTextureType(textures[idx].getType())); -// textures[idx] = null; -// } -// context.textureIndexList.copyNewToOld(); - } - - public void deleteImage(Image image) { - int texId = image.getId(); - if (texId != -1) { - intBuf1.put(0, texId); - intBuf1.position(0).limit(1); - glDeleteTextures(intBuf1); - image.resetObject(); - - statistics.onDeleteTexture(); - } - } - - /*********************************************************************\ - |* Vertex Buffers and Attributes *| - \*********************************************************************/ - private int convertUsage(Usage usage) { - switch (usage) { - case Static: - return GL_STATIC_DRAW; - case Dynamic: - return GL_DYNAMIC_DRAW; - case Stream: - return GL_STREAM_DRAW; - default: - throw new UnsupportedOperationException("Unknown usage type."); - } - } - - private int convertFormat(Format format) { - switch (format) { - case Byte: - return GL_BYTE; - case UnsignedByte: - return GL_UNSIGNED_BYTE; - case Short: - return GL_SHORT; - case UnsignedShort: - return GL_UNSIGNED_SHORT; - case Int: - return GL_INT; - case UnsignedInt: - return GL_UNSIGNED_INT; - case Float: - return GL_FLOAT; - case Double: - return GL_DOUBLE; - default: - throw new UnsupportedOperationException("Unknown buffer format."); - - } - } - - public void updateBufferData(VertexBuffer vb) { - int bufId = vb.getId(); - boolean created = false; - if (bufId == -1) { - // create buffer - glGenBuffers(intBuf1); - bufId = intBuf1.get(0); - vb.setId(bufId); - objManager.registerObject(vb); - - //statistics.onNewVertexBuffer(); - - created = true; - } - - // bind buffer - int target; - if (vb.getBufferType() == VertexBuffer.Type.Index) { - target = GL_ELEMENT_ARRAY_BUFFER; - if (context.boundElementArrayVBO != bufId) { - glBindBuffer(target, bufId); - context.boundElementArrayVBO = bufId; - //statistics.onVertexBufferUse(vb, true); - } else { - //statistics.onVertexBufferUse(vb, false); - } - } else { - target = GL_ARRAY_BUFFER; - if (context.boundArrayVBO != bufId) { - glBindBuffer(target, bufId); - context.boundArrayVBO = bufId; - //statistics.onVertexBufferUse(vb, true); - } else { - //statistics.onVertexBufferUse(vb, false); - } - } - - int usage = convertUsage(vb.getUsage()); - vb.getData().rewind(); - - if (created || vb.hasDataSizeChanged()) { - // upload data based on format - switch (vb.getFormat()) { - case Byte: - case UnsignedByte: - glBufferData(target, (ByteBuffer) vb.getData(), usage); - break; - // case Half: - case Short: - case UnsignedShort: - glBufferData(target, (ShortBuffer) vb.getData(), usage); - break; - case Int: - case UnsignedInt: - glBufferData(target, (IntBuffer) vb.getData(), usage); - break; - case Float: - glBufferData(target, (FloatBuffer) vb.getData(), usage); - break; - case Double: - glBufferData(target, (DoubleBuffer) vb.getData(), usage); - break; - default: - throw new UnsupportedOperationException("Unknown buffer format."); - } - } else { - // Invalidate buffer data (orphan) before uploading new data. - - - switch (vb.getFormat()) { - case Byte: - case UnsignedByte: - glBufferSubData(target, 0, (ByteBuffer) vb.getData()); - break; - case Short: - case UnsignedShort: - glBufferSubData(target, 0, (ShortBuffer) vb.getData()); - break; - case Int: - case UnsignedInt: - glBufferSubData(target, 0, (IntBuffer) vb.getData()); - break; - case Float: - glBufferSubData(target, 0, (FloatBuffer) vb.getData()); - break; - case Double: - glBufferSubData(target, 0, (DoubleBuffer) vb.getData()); - break; - default: - throw new UnsupportedOperationException("Unknown buffer format."); - } - } - - vb.clearUpdateNeeded(); - } - - public void deleteBuffer(VertexBuffer vb) { - int bufId = vb.getId(); - if (bufId != -1) { - // delete buffer - intBuf1.put(0, bufId); - intBuf1.position(0).limit(1); - glDeleteBuffers(intBuf1); - vb.resetObject(); - - //statistics.onDeleteVertexBuffer(); - } - } - - public void clearVertexAttribs() { - IDList attribList = context.attribIndexList; - for (int i = 0; i < attribList.oldLen; i++) { - int idx = attribList.oldList[i]; - glDisableVertexAttribArray(idx); - if (context.boundAttribs[idx].isInstanced()) { - glVertexAttribDivisorARB(idx, 0); - } - context.boundAttribs[idx] = null; - } - context.attribIndexList.copyNewToOld(); - } - - public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { - if (vb.getBufferType() == VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); - } - - int programId = context.boundShaderProgram; - - if (programId > 0) { - Attribute attrib = context.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(); - loc = glGetAttribLocation(programId, nameBuf); - - // 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); - } - } - - if (vb.isInstanced()) { - if (!caps.contains(Caps.MeshInstancing)) { - throw new RendererException("Instancing is required, " - + "but not supported by the " - + "graphics hardware"); - } - } - int slotsRequired = 1; - if (vb.getNumComponents() > 4) { - if (vb.getNumComponents() % 4 != 0) { - throw new RendererException("Number of components in multi-slot " - + "buffers must be divisible by 4"); - } - slotsRequired = vb.getNumComponents() / 4; - } - - if (vb.isUpdateNeeded() && idb == null) { - updateBufferData(vb); - } - - VertexBuffer[] attribs = context.boundAttribs; - for (int i = 0; i < slotsRequired; i++) { - if (!context.attribIndexList.moveToNew(loc + i)) { - glEnableVertexAttribArray(loc + i); - //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 (context.boundArrayVBO != bufId) { - glBindBuffer(GL_ARRAY_BUFFER, bufId); - context.boundArrayVBO = bufId; - //statistics.onVertexBufferUse(vb, true); - } else { - //statistics.onVertexBufferUse(vb, false); - } - - if (slotsRequired == 1) { - glVertexAttribPointer(loc, - vb.getNumComponents(), - convertFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - vb.getOffset()); - } else { - for (int i = 0; i < slotsRequired; i++) { - // The pointer maps the next 4 floats in the slot. - // E.g. - // P1: XXXX____________XXXX____________ - // P2: ____XXXX____________XXXX________ - // P3: ________XXXX____________XXXX____ - // P4: ____________XXXX____________XXXX - // stride = 4 bytes in float * 4 floats in slot * num slots - // offset = 4 bytes in float * 4 floats in slot * slot index - glVertexAttribPointer(loc + i, - 4, - convertFormat(vb.getFormat()), - vb.isNormalized(), - 4 * 4 * slotsRequired, - 4 * 4 * i); - } - } - - for (int i = 0; i < slotsRequired; i++) { - int slot = loc + i; - if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) { - // non-instanced -> instanced - glVertexAttribDivisorARB(slot, vb.getInstanceSpan()); - } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) { - // instanced -> non-instanced - glVertexAttribDivisorARB(slot, 0); - } - attribs[slot] = 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) { - boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - if (useInstancing) { - glDrawArraysInstancedARB(convertElementMode(mode), 0, - vertCount, count); - } else { - glDrawArrays(convertElementMode(mode), 0, vertCount); - } - } - - 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 (context.boundElementArrayVBO != bufId) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufId); - context.boundElementArrayVBO = bufId; - //statistics.onVertexBufferUse(indexBuf, true); - } else { - //statistics.onVertexBufferUse(indexBuf, true); - } - - int vertCount = mesh.getVertexCount(); - boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - - if (mesh.getMode() == Mode.Hybrid) { - int[] modeStart = mesh.getModeStart(); - int[] elementLengths = mesh.getElementLengths(); - - int elMode = convertElementMode(Mode.Triangles); - int fmt = convertFormat(indexBuf.getFormat()); - int elSize = indexBuf.getFormat().getComponentSize(); - int listStart = modeStart[0]; - int stripStart = modeStart[1]; - int fanStart = modeStart[2]; - int curOffset = 0; - for (int i = 0; i < elementLengths.length; i++) { - if (i == stripStart) { - elMode = convertElementMode(Mode.TriangleStrip); - } else if (i == fanStart) { - elMode = convertElementMode(Mode.TriangleFan); - } - int elementLength = elementLengths[i]; - - if (useInstancing) { - glDrawElementsInstancedARB(elMode, - elementLength, - fmt, - curOffset, - count); - } else { - glDrawRangeElements(elMode, - 0, - vertCount, - elementLength, - fmt, - curOffset); - } - - curOffset += elementLength * elSize; - } - } else { - if (useInstancing) { - glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertFormat(indexBuf.getFormat()), - 0, - count); - } else { - glDrawRangeElements(convertElementMode(mesh.getMode()), - 0, - vertCount, - indexBuf.getData().limit(), - convertFormat(indexBuf.getFormat()), - 0); - } - } - } - - /*********************************************************************\ - |* Render Calls *| - \*********************************************************************/ - public int convertElementMode(Mesh.Mode mode) { - switch (mode) { - case Points: - return GL_POINTS; - case Lines: - return GL_LINES; - case LineLoop: - return GL_LINE_LOOP; - case LineStrip: - return GL_LINE_STRIP; - case Triangles: - return GL_TRIANGLES; - case TriangleFan: - return GL_TRIANGLE_FAN; - case TriangleStrip: - return GL_TRIANGLE_STRIP; - default: - throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); - } - } - - public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) { - int id = mesh.getId(); - if (id == -1) { - IntBuffer temp = intBuf1; - glGenVertexArrays(temp); - id = temp.get(0); - mesh.setId(id); - } - - if (context.boundVertexArray != id) { - glBindVertexArray(id); - context.boundVertexArray = id; - } - - VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); - if (interleavedData != null && interleavedData.isUpdateNeeded()) { - updateBufferData(interleavedData); - } - - if (instanceData != null) { - setVertexAttrib(instanceData, null); - } - - for (VertexBuffer vb : mesh.getBufferList().getArray()) { - if (vb.getBufferType() == Type.InterleavedData - || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers - || vb.getBufferType() == Type.Index) { - continue; - } - - if (vb.getStride() == 0) { - // not interleaved - setVertexAttrib(vb); - } else { - // interleaved - setVertexAttrib(vb, interleavedData); - } - } - } - - private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) { - if (mesh.getId() == -1) { - updateVertexArray(mesh, instanceData); - } else { - // TODO: Check if it was updated - } - - if (context.boundVertexArray != mesh.getId()) { - glBindVertexArray(mesh.getId()); - context.boundVertexArray = mesh.getId(); - } - -// IntMap buffers = mesh.getBuffers(); - VertexBuffer indices; - if (mesh.getNumLodLevels() > 0) { - indices = mesh.getLodLevel(lod); - } else { - indices = mesh.getBuffer(Type.Index); - } - if (indices != null) { - drawTriangleList(indices, mesh, count); - } else { - drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); - } - clearVertexAttribs(); - clearTextureUnits(); - } - - private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - - // Here while count is still passed in. Can be removed when/if - // the method is collapsed again. -pspeed - count = Math.max(mesh.getInstanceCount(), count); - - VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); - if (interleavedData != null && interleavedData.isUpdateNeeded()) { - updateBufferData(interleavedData); - } - - VertexBuffer indices; - if (mesh.getNumLodLevels() > 0) { - indices = mesh.getLodLevel(lod); - } else { - indices = mesh.getBuffer(Type.Index); - } - - if (instanceData != null) { - for (VertexBuffer vb : instanceData) { - setVertexAttrib(vb, null); - } - } - - for (VertexBuffer vb : mesh.getBufferList().getArray()) { - if (vb.getBufferType() == Type.InterleavedData - || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers - || vb.getBufferType() == Type.Index) { - continue; - } - - if (vb.getStride() == 0) { - // not interleaved - setVertexAttrib(vb); - } else { - // interleaved - setVertexAttrib(vb, interleavedData); - } - } - - if (indices != null) { - drawTriangleList(indices, mesh, count); - } else { - drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); - } - clearVertexAttribs(); - clearTextureUnits(); - } - - public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - if (mesh.getVertexCount() == 0) { - 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) { - glActiveTexture(GL_TEXTURE0); - context.boundTextureUnit = 0; - } - glDisable(GL_POINT_SPRITE); - glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); - context.pointSprite = false; - } - } - - if (context.pointSize != mesh.getPointSize()) { - glPointSize(mesh.getPointSize()); - context.pointSize = mesh.getPointSize(); - } - if (context.lineWidth != mesh.getLineWidth()) { - glLineWidth(mesh.getLineWidth()); - context.lineWidth = mesh.getLineWidth(); - } - - statistics.onMeshDrawn(mesh, lod, count); -// if (ctxCaps.GL_ARB_vertex_array_object){ -// renderMeshVertexArray(mesh, lod, count); -// }else{ - renderMeshDefault(mesh, lod, count, instanceData); -// } - } - - public void setMainFrameBufferSrgb(boolean enableSrgb) { - // Gamma correction - - if (!caps.contains(Caps.Srgb)) { - // Not supported, sorry. - - logger.warning("sRGB framebuffer is not supported " + - "by video hardware, but was requested."); - - return; - } - - setFrameBuffer(null); - - if (enableSrgb) { - if (!glGetBoolean(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT)) { - logger.warning("Driver claims that default framebuffer " - + "is not sRGB capable. Enabling anyway."); - } - - - - glEnable(GL_FRAMEBUFFER_SRGB_EXT); - - logger.log(Level.FINER, "SRGB FrameBuffer enabled (Gamma Correction)"); - } else { - glDisable(GL_FRAMEBUFFER_SRGB_EXT); - } - } - - public void setLinearizeSrgbImages(boolean linearize) { - if (caps.contains(Caps.Srgb)) { - linearizeSrgbImages = linearize; - } - } -} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java deleted file mode 100644 index 41dddd6ae..000000000 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java +++ /dev/null @@ -1,514 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.renderer.lwjgl; - -import com.jme3.renderer.Caps; -import com.jme3.renderer.RendererException; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ColorSpace; -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.logging.Level; -import java.util.logging.Logger; -import static org.lwjgl.opengl.ARBDepthBufferFloat.*; -import static org.lwjgl.opengl.ARBES3Compatibility.*; -import static org.lwjgl.opengl.ARBHalfFloatPixel.*; -import static org.lwjgl.opengl.ARBTextureFloat.*; -import static org.lwjgl.opengl.ARBTextureMultisample.*; -import static org.lwjgl.opengl.EXTPackedDepthStencil.*; -import static org.lwjgl.opengl.EXTPackedFloat.*; -import static org.lwjgl.opengl.EXTTextureArray.*; -import static org.lwjgl.opengl.EXTTextureCompressionS3TC.*; -import static org.lwjgl.opengl.EXTTextureSRGB.*; -import static org.lwjgl.opengl.EXTTextureSharedExponent.*; -import static org.lwjgl.opengl.GL11.*; -import static org.lwjgl.opengl.GL12.*; -import static org.lwjgl.opengl.GL13.*; -import static org.lwjgl.opengl.GL14.*; - -/** - * - * Should not be used, has been replaced by Unified Rendering Architechture. - * @deprecated - */ -@Deprecated -class TextureUtil { - - static class GLImageFormat { - - int internalFormat; - int format; - int dataType; - boolean compressed; - - public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { - this.internalFormat = internalFormat; - this.format = format; - this.dataType = dataType; - this.compressed = compressed; - } - } - - private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length]; - - private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){ - formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed); - } - - static { - // Alpha formats - setFormat(Format.Alpha8, GL_ALPHA8, GL_ALPHA, GL_UNSIGNED_BYTE, false); - - // Luminance formats - setFormat(Format.Luminance8, GL_LUMINANCE8, GL_LUMINANCE, GL_UNSIGNED_BYTE, false); - setFormat(Format.Luminance16F, GL_LUMINANCE16F_ARB, GL_LUMINANCE, GL_HALF_FLOAT_ARB, false); - setFormat(Format.Luminance32F, GL_LUMINANCE32F_ARB, GL_LUMINANCE, GL_FLOAT, false); - - // Luminance alpha formats - setFormat(Format.Luminance8Alpha8, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, false); - setFormat(Format.Luminance16FAlpha16F, GL_LUMINANCE_ALPHA16F_ARB, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_ARB, false); - - // Depth formats - setFormat(Format.Depth, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, false); - setFormat(Format.Depth16, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); - setFormat(Format.Depth24, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, false); - setFormat(Format.Depth32, GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, false); - setFormat(Format.Depth32F, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, false); - - // Depth stencil formats - setFormat(Format.Depth24Stencil8, GL_DEPTH24_STENCIL8_EXT, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, false); - - // RGB formats - setFormat(Format.BGR8, GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE, false); - setFormat(Format.ARGB8, GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, false); - setFormat(Format.BGRA8, GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, false); - setFormat(Format.RGB8, GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, false); - setFormat(Format.RGB16F, GL_RGB16F_ARB, GL_RGB, GL_HALF_FLOAT_ARB, false); - setFormat(Format.RGB32F, GL_RGB32F_ARB, GL_RGB, GL_FLOAT, false); - - // Special RGB formats - setFormat(Format.RGB111110F, GL_R11F_G11F_B10F_EXT, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV_EXT, false); - setFormat(Format.RGB9E5, GL_RGB9_E5_EXT, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV_EXT, false); - setFormat(Format.RGB16F_to_RGB111110F, GL_R11F_G11F_B10F_EXT, GL_RGB, GL_HALF_FLOAT_ARB, false); - setFormat(Format.RGB16F_to_RGB9E5, GL_RGB9_E5_EXT, GL_RGB, GL_HALF_FLOAT_ARB, false); - - // RGBA formats - setFormat(Format.ABGR8, GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, false); - setFormat(Format.RGB5A1, GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, false); - setFormat(Format.RGBA8, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false); - setFormat(Format.RGBA16F, GL_RGBA16F_ARB, GL_RGBA, GL_HALF_FLOAT_ARB, false); - setFormat(Format.RGBA32F, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT, false); - - // DXT formats - setFormat(Format.DXT1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_BYTE, true); - setFormat(Format.DXT1A, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_BYTE, true); - setFormat(Format.DXT3, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_BYTE, true); - setFormat(Format.DXT5, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_BYTE, true); - - // ETC1 support on regular OpenGL requires ES3 compatibility extension. - // NOTE: ETC2 is backwards compatible with ETC1, so we can - // upload ETC1 textures as ETC2. - setFormat(Format.ETC1, GL_COMPRESSED_RGB8_ETC2, GL_RGB, GL_UNSIGNED_BYTE, true); - } - - //sRGB formats - private static final GLImageFormat sRGB_RGB8 = new GLImageFormat(GL_SRGB8_EXT, GL_RGB, GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_RGBA8 = new GLImageFormat(GL_SRGB8_ALPHA8_EXT, GL_RGBA, GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_Luminance8 = new GLImageFormat(GL_SLUMINANCE8_EXT, GL_LUMINANCE, GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_LuminanceAlpha8 = new GLImageFormat(GL_SLUMINANCE8_ALPHA8_EXT, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_BGR8 = new GLImageFormat(GL_SRGB8_EXT, GL_BGR, GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_ABGR8 = new GLImageFormat(GL_SRGB8_ALPHA8_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, false); - private static final GLImageFormat sRGB_ARGB8 = new GLImageFormat(GL_SRGB8_ALPHA8_EXT, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, false); - private static final GLImageFormat sRGB_BGRA8 = new GLImageFormat(GL_SRGB8_ALPHA8_EXT, GL_BGRA, GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_DXT1 = new GLImageFormat(GL_COMPRESSED_SRGB_S3TC_DXT1_EXT,GL_RGB, GL_UNSIGNED_BYTE, true); - private static final GLImageFormat sRGB_DXT1A = new GLImageFormat(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_BYTE, true); - private static final GLImageFormat sRGB_DXT3 = new GLImageFormat(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_BYTE, true); - private static final GLImageFormat sRGB_DXT5 = new GLImageFormat(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_BYTE, true); - - public static GLImageFormat getImageFormat(EnumSet caps, Format fmt, boolean isSrgb){ - switch (fmt){ - case ETC1: - if (!caps.contains(Caps.TextureCompressionETC1)) { - return null; - } - break; - case DXT1: - case DXT1A: - case DXT3: - case DXT5: - if (!caps.contains(Caps.TextureCompressionS3TC)) { - return null; - } - break; - case Depth24Stencil8: - if (!caps.contains(Caps.PackedDepthStencilBuffer)){ - return null; - } - break; - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - case RGB16F: - case RGB32F: - case RGBA16F: - case RGBA32F: - if (!caps.contains(Caps.FloatTexture)){ - return null; - } - break; - case Depth32F: - if (!caps.contains(Caps.FloatDepthBuffer)){ - return null; - } - break; - case RGB9E5: - case RGB16F_to_RGB9E5: - if (!caps.contains(Caps.SharedExponentTexture)){ - return null; - } - break; - case RGB111110F: - case RGB16F_to_RGB111110F: - if (!caps.contains(Caps.PackedFloatTexture)){ - return null; - } - break; - } - if (isSrgb) { - return getSrgbFormat(fmt); - } else { - return formatToGL[fmt.ordinal()]; - } - } - - public static GLImageFormat getImageFormatWithError(EnumSet caps, Format fmt, boolean isSrgb) { - GLImageFormat glFmt = getImageFormat(caps, fmt, isSrgb); - if (glFmt == null) { - throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); - } - return glFmt; - } - - private static GLImageFormat getSrgbFormat(Format fmt){ - switch (fmt) { - case RGB8: - return sRGB_RGB8; - case RGBA8: - return sRGB_RGBA8; - case BGR8: - return sRGB_BGR8; - case ABGR8: - return sRGB_ABGR8; - case ARGB8: - return sRGB_ARGB8; - case BGRA8: - return sRGB_BGRA8; - case Luminance8: - return sRGB_Luminance8; - case Luminance8Alpha8: - return sRGB_LuminanceAlpha8; - case DXT1: - return sRGB_DXT1; - case DXT1A: - return sRGB_DXT1A; - case DXT3: - return sRGB_DXT3; - case DXT5: - return sRGB_DXT5; - default: - Logger.getLogger(TextureUtil.class.getName()).log(Level.WARNING, "Format {0} has no sRGB equivalent, using linear format.", fmt.toString()); - return formatToGL[fmt.ordinal()]; - } - } - - public static void uploadTexture(EnumSet caps, - Image image, - int target, - int index, - int border, - boolean linearizeSrgb){ - - Image.Format fmt = image.getFormat(); - GLImageFormat glFmt = getImageFormatWithError(caps, fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb); - - ByteBuffer data; - if (index >= 0 && image.getData() != null && image.getData().size() > 0){ - data = image.getData(index); - }else{ - data = null; - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - - if (data != null) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - } - - int[] mipSizes = image.getMipMapSizes(); - int pos = 0; - // TODO: Remove unneccessary allocation - if (mipSizes == null){ - if (data != null) - mipSizes = new int[]{ data.capacity() }; - else - mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; - } - - boolean subtex = false; - int samples = image.getMultiSamples(); - - for (int i = 0; i < mipSizes.length; i++){ - int mipWidth = Math.max(1, width >> i); - int mipHeight = Math.max(1, height >> i); - int mipDepth = Math.max(1, depth >> i); - - if (data != null){ - data.position(pos); - data.limit(pos + mipSizes[i]); - } - - if (glFmt.compressed && data != null){ - if (target == GL_TEXTURE_3D){ - glCompressedTexImage3D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - mipDepth, - border, - data); - } else if (target == GL_TEXTURE_2D_ARRAY_EXT) { - // Upload compressed texture array slice - glCompressedTexSubImage3D(target, - i, - 0, - 0, - index, - mipWidth, - mipHeight, - 1, - glFmt.internalFormat, - data); - }else{ - //all other targets use 2D: array, cubemap, 2d - glCompressedTexImage2D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - border, - data); - } - }else{ - if (target == GL_TEXTURE_3D){ - glTexImage3D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - mipDepth, - border, - glFmt.format, - glFmt.dataType, - data); - }else if (target == GL_TEXTURE_2D_ARRAY_EXT){ - // prepare data for 2D array - // or upload slice - if (index == -1){ - glTexImage3D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - image.getData().size(), //# of slices - border, - glFmt.format, - glFmt.dataType, - data); - }else{ - glTexSubImage3D(target, - i, // level - 0, // xoffset - 0, // yoffset - index, // zoffset - mipWidth, // width - mipHeight, // height - 1, // depth - glFmt.format, - glFmt.dataType, - data); - } - }else{ - if (subtex){ - if (samples > 1){ - throw new IllegalStateException("Cannot update multisample textures"); - } - - glTexSubImage2D(target, - i, - 0, 0, - mipWidth, mipHeight, - glFmt.format, - glFmt.dataType, - data); - }else{ - if (samples > 1){ - glTexImage2DMultisample(target, - samples, - glFmt.internalFormat, - mipWidth, - mipHeight, - true); - }else{ - glTexImage2D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - border, - glFmt.format, - glFmt.dataType, - data); - } - } - } - } - - 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( - EnumSet caps, - Image image, - int target, - int index, - int x, - int y, - boolean linearizeSrgb) { - Image.Format fmt = image.getFormat(); - GLImageFormat glFmt = getImageFormatWithError(caps, fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb); - - ByteBuffer data = null; - if (index >= 0 && image.getData() != null && image.getData().size() > 0) { - data = image.getData(index); - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - - if (data != null) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - } - - int[] mipSizes = image.getMipMapSizes(); - int pos = 0; - - // TODO: Remove unneccessary allocation - if (mipSizes == null){ - if (data != null) { - mipSizes = new int[]{ data.capacity() }; - } else { - mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; - } - } - - int samples = image.getMultiSamples(); - - for (int i = 0; i < mipSizes.length; i++){ - int mipWidth = Math.max(1, width >> i); - int mipHeight = Math.max(1, height >> i); - int mipDepth = Math.max(1, depth >> i); - - if (data != null){ - data.position(pos); - data.limit(pos + mipSizes[i]); - } - - // to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each - // gl*Image call in an attempt to unclutter things a bit - pos += mipSizes[i]; - - int glFmtInternal = glFmt.internalFormat; - int glFmtFormat = glFmt.format; - int glFmtDataType = glFmt.dataType; - - if (glFmt.compressed && data != null){ - if (target == GL_TEXTURE_3D){ - glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data); - continue; - } - - // all other targets use 2D: array, cubemap, 2d - glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data); - continue; - } - - if (target == GL_TEXTURE_3D){ - glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); - continue; - } - - if (target == GL_TEXTURE_2D_ARRAY_EXT){ - // prepare data for 2D array or upload slice - if (index == -1){ - glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); - continue; - } - - glTexSubImage3D(target, i, x, y, index, width, height, 1, glFmtFormat, glFmtDataType, data); - continue; - } - - if (samples > 1){ - throw new IllegalStateException("Cannot update multisample textures"); - } - - glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data); - } - } -} From 0dc77b4d6e0f37ae3ad019d16b964ddebaee5e59 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 11 May 2015 19:32:48 -0400 Subject: [PATCH 177/225] GLRenderer: remove commented out limits --- .../java/com/jme3/renderer/opengl/GLRenderer.java | 15 --------------- 1 file changed, 15 deletions(-) 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 66f4d66d3..8907a4104 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 @@ -84,21 +84,6 @@ public class GLRenderer implements Renderer { private final EnumSet caps = EnumSet.noneOf(Caps.class); private final EnumMap limits = new EnumMap(Limits.class); -// private int vertexTextureUnits; -// private int fragTextureUnits; -// private int vertexUniforms; -// private int fragUniforms; -// private int vertexAttribs; -// private int maxFBOSamples; -// private int maxFBOAttachs; -// private int maxMRTFBOAttachs; -// private int maxRBSize; -// private int maxTexSize; -// private int maxCubeTexSize; -// private int maxVertCount; -// private int maxTriCount; -// private int maxColorTexSamples; -// private int maxDepthTexSamples; private FrameBuffer mainFbOverride = null; private final Statistics statistics = new Statistics(); private int vpX, vpY, vpW, vpH; From 5002413e516ddf6540d97e634c7ad298ca03a679 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 11 May 2015 19:33:35 -0400 Subject: [PATCH 178/225] GLRenderer: fix wrap / filter modes * They were not configured properly due to an old workaround --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 4 ---- 1 file changed, 4 deletions(-) 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 8907a4104..3f14bdb10 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 @@ -1843,10 +1843,6 @@ public class GLRenderer implements Renderer { } } - if (context.pointSprite) { - return; // Attempt to fix glTexParameter crash for some ATI GPUs - } - // repeat modes switch (tex.getType()) { case ThreeDimensional: From 04f6b01d2e01b6ab8b5a0ab7898b70f39395cffd Mon Sep 17 00:00:00 2001 From: Nehon Date: Tue, 12 May 2015 21:13:17 +0200 Subject: [PATCH 179/225] Reverted commit https://github.com/jMonkeyEngine/jmonkeyengine/commit/3f3ef99b8693cfab8ebc2e74c766ddb504ca4a16 as it was not valid and introducing another issue. --- jme3-core/src/main/java/com/jme3/post/Filter.java | 5 ++--- .../src/main/java/com/jme3/post/FilterPostProcessor.java | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java index a3c136e55..a6e2e3c01 100644 --- a/jme3-core/src/main/java/com/jme3/post/Filter.java +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -233,12 +233,11 @@ public abstract class Filter implements Savable { * @param vp the viewport * @param w the width * @param h the height - * @param numSamples the number of samples for anti aliasing */ - protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h, int numSamples) { + protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { // cleanup(renderManager.getRenderer()); defaultPass = new Pass(); - defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat(), numSamples); + defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat()); initFilter(manager, renderManager, vp, w, h); } diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java index 462f03314..2cd8b83f8 100644 --- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -170,10 +170,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable { renderFrameBuffer.setDepthTexture(depthTexture); } computeDepth = true; - filter.init(assetManager, renderManager, vp, width, height, numSamples); + filter.init(assetManager, renderManager, vp, width, height); filter.setDepthTexture(depthTexture); } else { - filter.init(assetManager, renderManager, vp, width, height, numSamples); + filter.init(assetManager, renderManager, vp, width, height); } } From 14c5304f27e45a366888ee784fb9a221938af3b0 Mon Sep 17 00:00:00 2001 From: Nehon Date: Tue, 12 May 2015 21:15:14 +0200 Subject: [PATCH 180/225] Fixed defines tests in multisample.glsllib so that one can have a single sampled beck buffer and a multisampled depth buffer without having the shader to crash. Also optimized the way glsl 150 texture vs texture2D is handled --- .../Common/ShaderLib/MultiSample.glsllib | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib index bea736f9d..3b06e6441 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib @@ -15,8 +15,14 @@ uniform int m_NumSamplesDepth; #define DEPTHTEXTURE sampler2D #endif -// NOTE: Only define multisample functions if multisample is available and is being used! -#if defined(GL_ARB_texture_multisample) && (defined(RESOLVE_MS) || defined(RESOLVE_DEPTH_MS)) +#if __VERSION__ >= 150 + #define TEXTURE texture +#else + #define TEXTURE texture2D +#endif + +// NOTE: Only define multisample functions if multisample is available +#if defined(GL_ARB_texture_multisample) vec4 textureFetch(in sampler2DMS tex,in vec2 texC, in int numSamples){ ivec2 iTexC = ivec2(texC * vec2(textureSize(tex))); vec4 color = vec4(0.0); @@ -44,40 +50,21 @@ vec4 getDepth(in sampler2DMS tex,in vec2 texC){ return textureFetch(tex,texC,m_NumSamplesDepth); } -#elif __VERSION__ >= 150 - -vec4 fetchTextureSample(in sampler2D tex,in vec2 texC,in int sample){ - return texture(tex,texC); -} - -vec4 getColor(in sampler2D tex, in vec2 texC){ - return texture(tex,texC); -} - -vec4 getColorSingle(in sampler2D tex, in vec2 texC){ - return texture(tex, texC); -} - -vec4 getDepth(in sampler2D tex,in vec2 texC){ - return texture(tex,texC); -} - -#else +#endif vec4 fetchTextureSample(in sampler2D tex,in vec2 texC,in int sample){ - return texture2D(tex,texC); + return TEXTURE(tex,texC); } vec4 getColor(in sampler2D tex, in vec2 texC){ - return texture2D(tex,texC); + return TEXTURE(tex,texC); } vec4 getColorSingle(in sampler2D tex, in vec2 texC){ - return texture2D(tex, texC); + return TEXTURE(tex, texC); } vec4 getDepth(in sampler2D tex,in vec2 texC){ - return texture2D(tex,texC); + return TEXTURE(tex,texC); } -#endif \ No newline at end of file From 92ab8b806035d89329e0b8ff64e16d5a0615fbef Mon Sep 17 00:00:00 2001 From: Maselbas Date: Thu, 14 May 2015 18:55:07 +0200 Subject: [PATCH 181/225] SDK SceneComposer : small modifications on the PickManager, now the camera rotation isn't cloned so it's no more necessary to update the rotation before the updataPick() --- .../jme3/gde/scenecomposer/tools/PickManager.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java index 22fa8478a..af4f11fe5 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java @@ -33,9 +33,9 @@ public class PickManager { private Spatial spatial; private SceneComposerToolController.TransformationType transformationType; - protected static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); - protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 - protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 + public static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); + public static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090 + public static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090 public PickManager() { @@ -75,7 +75,7 @@ public class PickManager { origineRotation = new Quaternion(Quaternion.IDENTITY); } else if (transformationType == SceneComposerToolController.TransformationType.camera) { rot.set(camera.getRotation()); - origineRotation = camera.getRotation().clone(); + origineRotation = camera.getRotation(); } plane.setLocalRotation(rot); } @@ -87,10 +87,6 @@ public class PickManager { * @return true if the the new picked location is set, else return false. */ public boolean updatePick(Camera camera, Vector2f screenCoord) { - if(transformationType == SceneComposerToolController.TransformationType.camera){ - origineRotation = camera.getRotation(); - plane.setLocalRotation(camera.getRotation()); - } finalPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null); return finalPickLoc != null; } From e27b8a5739cc5e6fe7e77875f705be60ca828d8f Mon Sep 17 00:00:00 2001 From: Maselbas Date: Thu, 14 May 2015 19:02:35 +0200 Subject: [PATCH 182/225] SDK SceneComposer : bugfix in the ShortcutManager, prevent from setting the currentShorcut to NULL when there are no new activable shortcuts --- .../tools/shortcuts/ShortcutManager.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java index 38548087d..5dad38d7c 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java @@ -66,8 +66,11 @@ public class ShortcutManager { } public boolean activateShortcut(KeyInputEvent kie) { - currentShortcut = getActivableShortcut(kie); - return isActive(); + ShortcutTool newShortcut = getActivableShortcut(kie); + if(newShortcut != null){ + currentShortcut = newShortcut; + } + return newShortcut != null; } public void doKeyPressed(KeyInputEvent kie) { @@ -182,13 +185,13 @@ public class ShortcutManager { public static boolean checkAxisKey(KeyInputEvent kie, Vector3f axisStore) { if (kie.getKeyCode() == KeyInput.KEY_X) { - axisStore = Vector3f.UNIT_X; + axisStore.set(Vector3f.UNIT_X); return true; } else if (kie.getKeyCode() == KeyInput.KEY_Y) { - axisStore = Vector3f.UNIT_Y; + axisStore.set(Vector3f.UNIT_Y); return true; } else if (kie.getKeyCode() == KeyInput.KEY_Z) { - axisStore = Vector3f.UNIT_Z; + axisStore.set(Vector3f.UNIT_Z); return true; } return false; From fb44f5fb7bfd58a971cec4838f314d18204559cd Mon Sep 17 00:00:00 2001 From: Maselbas Date: Thu, 14 May 2015 19:09:07 +0200 Subject: [PATCH 183/225] SDK SceneComposer : Added the new Move Shortcut, you can use this shortcut with all others tools --- .../SceneComposerToolController.java | 7 +- .../tools/shortcuts/MoveShortcut.java | 95 ++++++++++++++----- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java index 71fee925b..00d15bb2f 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java @@ -189,6 +189,7 @@ public class SceneComposerToolController extends SceneToolController { ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); if (scm.isActive()) { + scm.getActiveShortcut().setCamera(camera); scm.getActiveShortcut().actionPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } else if (editTool != null) { editTool.setCamera(camera); @@ -207,6 +208,7 @@ public class SceneComposerToolController extends SceneToolController { ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); if (scm.isActive()) { + scm.getActiveShortcut().setCamera(camera); scm.getActiveShortcut().actionSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } else if (editTool != null) { editTool.setCamera(camera); @@ -218,6 +220,7 @@ public class SceneComposerToolController extends SceneToolController { ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); if (scm.isActive()) { + scm.getActiveShortcut().setCamera(camera); scm.getActiveShortcut().mouseMoved(mouseLoc, rootNode, editorController.getCurrentDataObject(), selectedSpatial); } else if (editTool != null) { editTool.setCamera(camera); @@ -229,6 +232,7 @@ public class SceneComposerToolController extends SceneToolController { ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); if (scm.isActive()) { + scm.getActiveShortcut().setCamera(camera); scm.getActiveShortcut().draggedPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } else if (editTool != null) { editTool.setCamera(camera); @@ -240,6 +244,7 @@ public class SceneComposerToolController extends SceneToolController { ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); if (scm.isActive()) { + scm.getActiveShortcut().setCamera(null); scm.getActiveShortcut().draggedSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject()); } else if (editTool != null) { editTool.setCamera(camera); @@ -249,7 +254,7 @@ public class SceneComposerToolController extends SceneToolController { public void doKeyPressed(KeyInputEvent kie) { ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class); - + if (scm.isActive()) { scm.doKeyPressed(kie); } else { diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java index e59fec76e..3b0f22562 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java @@ -12,6 +12,7 @@ import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.tools.PickManager; import com.jme3.input.KeyInput; import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Node; @@ -28,18 +29,21 @@ public class MoveShortcut extends ShortcutTool { private Vector3f currentAxis; private StringBuilder numberBuilder; private Spatial spatial; - private Vector3f initalLocation; private Vector3f finalLocation; private PickManager pickManager; + private boolean pickEnabled; + private Vector3f startPosition; + private Vector3f finalPosition; @Override + public boolean isActivableBy(KeyInputEvent kie) { return kie.getKeyCode() == KeyInput.KEY_G; } @Override public void cancel() { - spatial.setLocalTranslation(initalLocation); + spatial.setLocalTranslation(startPosition); terminate(); } @@ -48,6 +52,14 @@ public class MoveShortcut extends ShortcutTool { terminate(); } + private void init(Spatial selectedSpatial) { + spatial = selectedSpatial; + startPosition = spatial.getLocalTranslation().clone(); + currentAxis = Vector3f.UNIT_XYZ; + pickManager = Lookup.getDefault().lookup(PickManager.class); + pickEnabled = false; + } + @Override public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates. @@ -56,27 +68,23 @@ public class MoveShortcut extends ShortcutTool { if (selectedSpatial == null) { terminate(); } else { - spatial = selectedSpatial; - initalLocation = spatial.getLocalTranslation(); - currentAxis = new Vector3f().set(Vector3f.UNIT_XYZ); - - pickManager = Lookup.getDefault().lookup(PickManager.class); - ///pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + init(selectedSpatial); } } @Override public void keyPressed(KeyInputEvent kie) { if (kie.isPressed()) { - - /* - ShortcutTool otherShortcut = Lookup.getDefault().lookup(ShortcutManager.class).getActivableShortcut(kie); - if(otherShortcut != null){ - Lookup.getDefault().lookup(ShortcutManager.class).setShortCut(otherShortcut); - }*/ + System.out.println(kie); Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie); - boolean axisChanged = ShortcutManager.checkAxisKey(kie, currentAxis); + Vector3f axis = new Vector3f(); + boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis); + if (axisChanged) { + currentAxis = axis; + System.out.println("AXIS : " + currentAxis); + + } boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder); boolean enterHit = ShortcutManager.checkEnterHit(kie); boolean escHit = ShortcutManager.checkEscHit(kie); @@ -85,11 +93,24 @@ public class MoveShortcut extends ShortcutTool { cancel(); } else if (enterHit) { apply(); + } else if (axisChanged && pickEnabled) { + //update pick manager + + if (currentAxis.equals(Vector3f.UNIT_X)) { + System.out.println("setTransformation X"); + pickManager.setTransformation(PickManager.PLANE_XY, getTransformType(), camera); + } else if (currentAxis.equals(Vector3f.UNIT_Y)) { + System.out.println("setTransformation Y"); + pickManager.setTransformation(PickManager.PLANE_YZ, getTransformType(), camera); + } else if (currentAxis.equals(Vector3f.UNIT_Z)) { + System.out.println("setTransformation Z"); + pickManager.setTransformation(PickManager.PLANE_XZ, getTransformType(), camera); + } } else if (axisChanged || numberChanged) { //update transformation float number = ShortcutManager.getNumberkey(numberBuilder); Vector3f translation = currentAxis.mult(number); - finalLocation = initalLocation.add(translation); + finalLocation = startPosition.add(translation); spatial.setLocalTranslation(finalLocation); } @@ -99,7 +120,7 @@ public class MoveShortcut extends ShortcutTool { @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { - if (!pressed) { + if (pressed) { apply(); } } @@ -113,13 +134,39 @@ public class MoveShortcut extends ShortcutTool { @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) { - pickManager.updatePick(camera, screenCoord); - /* PickManager pickManager = Lookup.getDefault().lookup(PickManager.class); - if (toolController.isSnapToScene()) { - moveManager.setAlternativePickTarget(rootNode.getLookup().lookup(Node.class)); - } - // free form translation - moveManager.move(camera, screenCoord, axis, toolController.isSnapToGrid());*/ + + if (!pickEnabled) { + if (currentAxis.equals(Vector3f.UNIT_XYZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), SceneComposerToolController.TransformationType.camera, camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else { + return; + } + } + + if (pickManager.updatePick(camera, screenCoord)) { + //pick update success + Vector3f diff; + + if (currentAxis.equals(Vector3f.UNIT_XYZ)) { + diff = pickManager.getTranslation(); + } else { + diff = pickManager.getTranslation(currentAxis); + } + Vector3f position = startPosition.add(diff); + finalPosition = position; + toolController.getSelectedSpatial().setLocalTranslation(position); + updateToolsTransformation(); + } } @Override From 6ef33e4c10750705d7c407e6c92fda9d62dc7b45 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Fri, 15 May 2015 17:57:47 +0200 Subject: [PATCH 184/225] SDK SceneComposer : added UndoRedo for the MoveShortcut --- .../tools/shortcuts/MoveShortcut.java | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java index 3b0f22562..a4dabb66b 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java @@ -6,8 +6,11 @@ package com.jme3.gde.scenecomposer.tools.shortcuts; import com.jme3.asset.AssetManager; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.tools.PickManager; import com.jme3.input.KeyInput; @@ -29,7 +32,6 @@ public class MoveShortcut extends ShortcutTool { private Vector3f currentAxis; private StringBuilder numberBuilder; private Spatial spatial; - private Vector3f finalLocation; private PickManager pickManager; private boolean pickEnabled; private Vector3f startPosition; @@ -48,7 +50,7 @@ public class MoveShortcut extends ShortcutTool { } private void apply() { - // TODO creat UNDO/REDO + actionPerformed(new MoveUndo(toolController.getSelectedSpatial(), startPosition, finalPosition)); terminate(); } @@ -110,8 +112,8 @@ public class MoveShortcut extends ShortcutTool { //update transformation float number = ShortcutManager.getNumberkey(numberBuilder); Vector3f translation = currentAxis.mult(number); - finalLocation = startPosition.add(translation); - spatial.setLocalTranslation(finalLocation); + finalPosition = startPosition.add(translation); + spatial.setLocalTranslation(finalPosition); } @@ -171,7 +173,9 @@ public class MoveShortcut extends ShortcutTool { @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + if (pressed) { + apply(); + } } @Override @@ -180,5 +184,51 @@ public class MoveShortcut extends ShortcutTool { cancel(); } } + + private class MoveUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + private Vector3f before = new Vector3f(), after = new Vector3f(); + + MoveUndo(Spatial spatial, Vector3f before, Vector3f after) { + this.spatial = spatial; + this.before.set(before); + if (after != null) { + this.after.set(after); + } + } + + @Override + public void sceneUndo() { + spatial.setLocalTranslation(before); + RigidBodyControl control = spatial.getControl(RigidBodyControl.class); + if (control != null) { + control.setPhysicsLocation(spatial.getWorldTranslation()); + } + CharacterControl character = spatial.getControl(CharacterControl.class); + if (character != null) { + character.setPhysicsLocation(spatial.getWorldTranslation()); + } + // toolController.selectedSpatialTransformed(); + } + + @Override + public void sceneRedo() { + spatial.setLocalTranslation(after); + RigidBodyControl control = spatial.getControl(RigidBodyControl.class); + if (control != null) { + control.setPhysicsLocation(spatial.getWorldTranslation()); + } + CharacterControl character = spatial.getControl(CharacterControl.class); + if (character != null) { + character.setPhysicsLocation(spatial.getWorldTranslation()); + } + //toolController.selectedSpatialTransformed(); + } + + public void setAfter(Vector3f after) { + this.after.set(after); + } + } } From 1f4ee34de8c4110da418d09ae1e7983ab499e82c Mon Sep 17 00:00:00 2001 From: Maselbas Date: Fri, 15 May 2015 18:43:45 +0200 Subject: [PATCH 185/225] SDK SceneComposer : add Rotate and Scale Shortcut, plus done some clean up into the MoveShorcut --- .../tools/shortcuts/MoveShortcut.java | 7 - .../tools/shortcuts/RotateShortcut.java | 189 +++++++++++++++++ .../tools/shortcuts/ScaleShortcut.java | 199 ++++++++++++++++++ .../tools/shortcuts/ShortcutManager.java | 11 +- 4 files changed, 398 insertions(+), 8 deletions(-) create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java index a4dabb66b..62cc70612 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java @@ -15,7 +15,6 @@ import com.jme3.gde.scenecomposer.SceneComposerToolController; import com.jme3.gde.scenecomposer.tools.PickManager; import com.jme3.input.KeyInput; import com.jme3.input.event.KeyInputEvent; -import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Node; @@ -77,15 +76,12 @@ public class MoveShortcut extends ShortcutTool { @Override public void keyPressed(KeyInputEvent kie) { if (kie.isPressed()) { - System.out.println(kie); Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie); Vector3f axis = new Vector3f(); boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis); if (axisChanged) { currentAxis = axis; - System.out.println("AXIS : " + currentAxis); - } boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder); boolean enterHit = ShortcutManager.checkEnterHit(kie); @@ -99,13 +95,10 @@ public class MoveShortcut extends ShortcutTool { //update pick manager if (currentAxis.equals(Vector3f.UNIT_X)) { - System.out.println("setTransformation X"); pickManager.setTransformation(PickManager.PLANE_XY, getTransformType(), camera); } else if (currentAxis.equals(Vector3f.UNIT_Y)) { - System.out.println("setTransformation Y"); pickManager.setTransformation(PickManager.PLANE_YZ, getTransformType(), camera); } else if (currentAxis.equals(Vector3f.UNIT_Z)) { - System.out.println("setTransformation Z"); pickManager.setTransformation(PickManager.PLANE_XZ, getTransformType(), camera); } } else if (axisChanged || numberChanged) { diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java new file mode 100644 index 000000000..1fb445b59 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java @@ -0,0 +1,189 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.asset.AssetManager; +import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; +import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; +import com.jme3.gde.scenecomposer.SceneComposerToolController; +import com.jme3.gde.scenecomposer.tools.PickManager; +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; + +/** + * + * @author dokthar + */ +public class RotateShortcut extends ShortcutTool { + + private Vector3f currentAxis; + private StringBuilder numberBuilder; + private Spatial spatial; + private PickManager pickManager; + private boolean pickEnabled; + private Quaternion startRotation; + private Quaternion finalRotation; + + @Override + + public boolean isActivableBy(KeyInputEvent kie) { + return kie.getKeyCode() == KeyInput.KEY_R; + } + + @Override + public void cancel() { + spatial.setLocalRotation(startRotation); + terminate(); + } + + private void apply() { + actionPerformed(new RotateUndo(toolController.getSelectedSpatial(), startRotation, finalRotation)); + terminate(); + } + + private void init(Spatial selectedSpatial) { + spatial = selectedSpatial; + startRotation = spatial.getLocalRotation().clone(); + currentAxis = Vector3f.UNIT_XYZ; + pickManager = Lookup.getDefault().lookup(PickManager.class); + pickEnabled = false; + } + + @Override + public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { + super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates. + hideMarker(); + numberBuilder = new StringBuilder(); + if (selectedSpatial == null) { + terminate(); + } else { + init(selectedSpatial); + } + } + + @Override + public void keyPressed(KeyInputEvent kie) { + if (kie.isPressed()) { + Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie); + + Vector3f axis = new Vector3f(); + boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis); + if (axisChanged) { + currentAxis = axis; + } + boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder); + boolean enterHit = ShortcutManager.checkEnterHit(kie); + boolean escHit = ShortcutManager.checkEscHit(kie); + + if (escHit) { + cancel(); + } else if (enterHit) { + apply(); + } else if (axisChanged && pickEnabled) { + pickEnabled = false; + spatial.setLocalRotation(startRotation.clone()); + } else if (axisChanged || numberChanged) { + //update transformation + /* float number = ShortcutManager.getNumberkey(numberBuilder); + Vector3f translation = currentAxis.mult(number); + finalPosition = startPosition.add(translation); + spatial.setLocalTranslation(finalPosition); + */ + } + + } + } + + @Override + public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + apply(); + } + } + + @Override + public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + cancel(); + } + } + + @Override + public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) { + + if (!pickEnabled) { + if (currentAxis.equals(Vector3f.UNIT_XYZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), SceneComposerToolController.TransformationType.camera, camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else { + return; + } + } + + if (pickManager.updatePick(camera, screenCoord)) { + + Quaternion rotation = startRotation.mult(pickManager.getRotation(startRotation.inverse())); + toolController.getSelectedSpatial().setLocalRotation(rotation); + finalRotation = rotation; + updateToolsTransformation(); + } + } + + @Override + public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + apply(); + } + } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); + } + } + + private class RotateUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + private Quaternion before, after; + + RotateUndo(Spatial spatial, Quaternion before, Quaternion after) { + this.spatial = spatial; + this.before = before; + this.after = after; + } + + @Override + public void sceneUndo() { + spatial.setLocalRotation(before); + toolController.selectedSpatialTransformed(); + } + + @Override + public void sceneRedo() { + spatial.setLocalRotation(after); + toolController.selectedSpatialTransformed(); + } + } +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java new file mode 100644 index 000000000..21966e385 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java @@ -0,0 +1,199 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.asset.AssetManager; +import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; +import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; +import com.jme3.gde.scenecomposer.SceneComposerToolController; +import com.jme3.gde.scenecomposer.tools.PickManager; +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; + +/** + * + * @author dokthar + */ +public class ScaleShortcut extends ShortcutTool { + + private Vector3f currentAxis; + private StringBuilder numberBuilder; + private Spatial spatial; + private PickManager pickManager; + private boolean pickEnabled; + private Vector3f startScale; + private Vector3f finalScale; + + @Override + + public boolean isActivableBy(KeyInputEvent kie) { + return kie.getKeyCode() == KeyInput.KEY_S; + } + + @Override + public void cancel() { + spatial.setLocalScale(startScale); + terminate(); + } + + private void apply() { + actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startScale, finalScale)); + terminate(); + } + + private void init(Spatial selectedSpatial) { + spatial = selectedSpatial; + startScale = spatial.getLocalScale().clone(); + currentAxis = Vector3f.UNIT_XYZ; + pickManager = Lookup.getDefault().lookup(PickManager.class); + pickEnabled = false; + } + + @Override + public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { + super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates. + hideMarker(); + numberBuilder = new StringBuilder(); + if (selectedSpatial == null) { + terminate(); + } else { + init(selectedSpatial); + } + } + + @Override + public void keyPressed(KeyInputEvent kie) { + if (kie.isPressed()) { + Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie); + + Vector3f axis = new Vector3f(); + boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis); + if (axisChanged) { + currentAxis = axis; + } + boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder); + boolean enterHit = ShortcutManager.checkEnterHit(kie); + boolean escHit = ShortcutManager.checkEscHit(kie); + + if (escHit) { + cancel(); + } else if (enterHit) { + apply(); + } else if (axisChanged && pickEnabled) { + pickEnabled = false; + } else if (axisChanged || numberChanged) { + //update transformation + /* float number = ShortcutManager.getNumberkey(numberBuilder); + Vector3f translation = currentAxis.mult(number); + finalPosition = startPosition.add(translation); + spatial.setLocalTranslation(finalPosition); + */ + } + + } + } + + @Override + public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + apply(); + } + } + + @Override + public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + if (pressed) { + cancel(); + } + } + + @Override + public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) { + + if (!pickEnabled) { + if (currentAxis.equals(Vector3f.UNIT_XYZ)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), SceneComposerToolController.TransformationType.camera, camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_X)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_Y)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else if (currentAxis.equals(Vector3f.UNIT_Z)) { + pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord); + pickEnabled = true; + } else { + return; + } + } + + if (pickManager.updatePick(camera, screenCoord)) { + Vector3f scale = startScale; + if (currentAxis.equals(Vector3f.UNIT_XYZ)) { + Vector3f constraintAxis = pickManager.getStartOffset().normalize(); + float diff = pickManager.getTranslation(constraintAxis).dot(constraintAxis); + diff *= 0.5f; + scale = startScale.add(new Vector3f(diff, diff, diff)); + } else { + // Get the translation in the spatial Space + Quaternion worldToSpatial = toolController.getSelectedSpatial().getWorldRotation().inverse(); + Vector3f diff = pickManager.getTranslation(worldToSpatial.mult(currentAxis)); + diff.multLocal(0.5f); + scale = startScale.add(diff); + } + finalScale = scale; + toolController.getSelectedSpatial().setLocalScale(scale); + updateToolsTransformation(); + } + } + + @Override + public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + apply(); + } + } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + if (pressed) { + cancel(); + } + } + + private class ScaleUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + 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); + toolController.selectedSpatialTransformed(); + } + + @Override + public void sceneRedo() { + spatial.setLocalScale(after); + toolController.selectedSpatialTransformed(); + } + } +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java index 5dad38d7c..bdbcfbe9e 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java @@ -26,6 +26,8 @@ public class ShortcutManager { public ShortcutManager() { shortcutList = new ArrayList(); shortcutList.add(new MoveShortcut()); + shortcutList.add(new RotateShortcut()); + shortcutList.add(new ScaleShortcut()); } /* @@ -72,7 +74,14 @@ public class ShortcutManager { } return newShortcut != null; } - + + /** + * This should be called to trigger the currentShortcut.keyPressed() method. + * This method do a first check for command key used to provide isCtrlDown, + * isShiftDown ect.. to de + * + * @param kie + */ public void doKeyPressed(KeyInputEvent kie) { ///todo check commande key if (isActive()) { From e9b1972aca2fb217ba79955c6926b63a1a7799a4 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Fri, 15 May 2015 18:47:14 +0200 Subject: [PATCH 186/225] SDK SceneComposer : clean up in the RotateTool, changed the name of the UndoableSceneEdit from ScaleUndo to RotateUndo --- .../com/jme3/gde/scenecomposer/tools/RotateTool.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java index 21e9a56d8..e5a125771 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java @@ -25,7 +25,6 @@ import org.openide.util.Lookup; public class RotateTool extends SceneEditTool { private Vector3f pickedMarker; - private Vector2f lastScreenCoord; private Quaternion startRotate; private Quaternion lastRotate; private boolean wasDragging = false; @@ -48,9 +47,8 @@ public class RotateTool extends SceneEditTool { if (!pressed) { setDefaultAxisMarkerColors(); pickedMarker = null; // mouse released, reset selection - lastScreenCoord = null; if (wasDragging) { - actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); + actionPerformed(new RotateUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); wasDragging = false; } pickManager.reset(); @@ -100,10 +98,9 @@ public class RotateTool extends SceneEditTool { if (!pressed) { setDefaultAxisMarkerColors(); pickedMarker = null; // mouse released, reset selection - lastScreenCoord = null; if (wasDragging) { - actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); + actionPerformed(new RotateUndo(toolController.getSelectedSpatial(), startRotate, lastRotate)); wasDragging = false; } pickManager.reset(); @@ -138,12 +135,12 @@ public class RotateTool extends SceneEditTool { } } - private class ScaleUndo extends AbstractUndoableSceneEdit { + private class RotateUndo extends AbstractUndoableSceneEdit { private Spatial spatial; private Quaternion before, after; - ScaleUndo(Spatial spatial, Quaternion before, Quaternion after) { + RotateUndo(Spatial spatial, Quaternion before, Quaternion after) { this.spatial = spatial; this.before = before; this.after = after; From bbca035a43abd5e77f1859c81418ceca8f329c1a Mon Sep 17 00:00:00 2001 From: Maselbas Date: Fri, 15 May 2015 20:36:55 +0200 Subject: [PATCH 187/225] SDK SceneComposer : all shortcuts tool are now outside of the selectTool, - added the Delete and Duplicate shortcut - deleted the MoveManager - clean up the SelectTool (removed old shortCuts) - now the ShortcutManager can also provide to SorthcutTools the state of ctr, alt and shift key --- .../gde/scenecomposer/tools/MoveManager.java | 204 ----- .../gde/scenecomposer/tools/SelectTool.java | 694 +----------------- .../tools/shortcuts/DeleteShortcut.java | 123 ++++ .../tools/shortcuts/DuplicateShortcut.java | 141 ++++ .../tools/shortcuts/ShortcutManager.java | 52 +- 5 files changed, 342 insertions(+), 872 deletions(-) delete mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveManager.java create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DeleteShortcut.java create mode 100644 sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveManager.java deleted file mode 100644 index d7b92db91..000000000 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveManager.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.gde.scenecomposer.tools; - -import com.jme3.bullet.control.CharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; -import com.jme3.gde.scenecomposer.SceneEditTool; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import org.openide.util.lookup.ServiceProvider; - -/** - * - * @author Nehon - */ -@ServiceProvider(service = MoveManager.class) -public class MoveManager { - - private Vector3f startLoc; - private Vector3f startWorldLoc; - private Vector3f lastLoc; - private Vector3f offset; - private Node alternativePickTarget = null; - private Node plane; - private Spatial spatial; - protected static final Quaternion XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0)); - protected static final Quaternion YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0)); - protected static final Quaternion XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); - //temp vars - private Quaternion rot = new Quaternion(); - private Vector3f newPos = new Vector3f(); - - public MoveManager() { - float size = 1000; - Geometry g = new Geometry("plane", new Quad(size, size)); - g.setLocalTranslation(-size / 2, -size / 2, 0); - plane = new Node(); - plane.attachChild(g); - } - - public Vector3f getOffset() { - return offset; - } - - public void reset() { - offset = null; - startLoc = null; - startWorldLoc = null; - lastLoc = null; - spatial = null; - alternativePickTarget = null; - } - - public void initiateMove(Spatial selectedSpatial, Quaternion planeRotation, boolean local) { - spatial = selectedSpatial; - startLoc = selectedSpatial.getLocalTranslation().clone(); - startWorldLoc = selectedSpatial.getWorldTranslation().clone(); - if (local) { - rot.set(selectedSpatial.getWorldRotation()); - plane.setLocalRotation(rot.multLocal(planeRotation)); - } else { - rot.set(planeRotation); - } - - plane.setLocalRotation(rot); - plane.setLocalTranslation(startWorldLoc); - - } - - public void updatePlaneRotation(Quaternion planeRotation) { - plane.setLocalRotation(rot); - } - - public boolean move(Camera camera, Vector2f screenCoord) { - return move(camera, screenCoord, Vector3f.UNIT_XYZ, false); - } - - public boolean move(Camera camera, Vector2f screenCoord, Vector3f constraintAxis, boolean gridSnap) { - Node toPick = alternativePickTarget == null ? plane : alternativePickTarget; - - Vector3f planeHit = SceneEditTool.pickWorldLocation(camera, screenCoord, toPick, alternativePickTarget == null ? null : spatial); - if (planeHit == null) { - return false; - } - - Spatial parent = spatial.getParent(); - //we are moving the root node, there is a slight chance that something went wrong. - if (parent == null) { - return false; - } - - //offset in world space - if (offset == null) { - offset = planeHit.subtract(spatial.getWorldTranslation()); // get the offset when we start so it doesn't jump - } - - newPos.set(planeHit).subtractLocal(offset); - - //constraining the translation with the contraintAxis. - Vector3f tmp = startWorldLoc.mult(Vector3f.UNIT_XYZ.subtract(constraintAxis)); - newPos.multLocal(constraintAxis).addLocal(tmp); - worldToLocalMove(gridSnap); - return true; - } - - private void worldToLocalMove(boolean gridSnap) { - //snap to grid (grid is assumed 1 WU per cell) - if (gridSnap) { - newPos.set(Math.round(newPos.x), Math.round(newPos.y), Math.round(newPos.z)); - } - - //computing the inverse world transform to get the new localtranslation - newPos.subtractLocal(spatial.getParent().getWorldTranslation()); - newPos = spatial.getParent().getWorldRotation().inverse().normalizeLocal().multLocal(newPos); - newPos.divideLocal(spatial.getParent().getWorldScale()); - - lastLoc = newPos; - spatial.setLocalTranslation(newPos); - - RigidBodyControl control = spatial.getControl(RigidBodyControl.class); - if (control != null) { - control.setPhysicsLocation(spatial.getWorldTranslation()); - } - CharacterControl character = spatial.getControl(CharacterControl.class); - if (character != null) { - character.setPhysicsLocation(spatial.getWorldTranslation()); - } - } - - public boolean moveAcross(Vector3f constraintAxis, float value, boolean gridSnap) { - newPos.set(startWorldLoc).addLocal(constraintAxis.mult(value)); - Spatial parent = spatial.getParent(); - //we are moving the root node, there is a slight chance that something went wrong. - if (parent == null) { - return false; - } - worldToLocalMove(gridSnap); - return true; - } - - public MoveUndo makeUndo() { - return new MoveUndo(spatial, startLoc, lastLoc); - } - - public void setAlternativePickTarget(Node alternativePickTarget) { - this.alternativePickTarget = alternativePickTarget; - } - - protected class MoveUndo extends AbstractUndoableSceneEdit { - - private Spatial spatial; - private Vector3f before = new Vector3f(), after = new Vector3f(); - - MoveUndo(Spatial spatial, Vector3f before, Vector3f after) { - this.spatial = spatial; - this.before.set(before); - if (after != null) { - this.after.set(after); - } - } - - @Override - public void sceneUndo() { - spatial.setLocalTranslation(before); - RigidBodyControl control = spatial.getControl(RigidBodyControl.class); - if (control != null) { - control.setPhysicsLocation(spatial.getWorldTranslation()); - } - CharacterControl character = spatial.getControl(CharacterControl.class); - if (character != null) { - character.setPhysicsLocation(spatial.getWorldTranslation()); - } - // toolController.selectedSpatialTransformed(); - } - - @Override - public void sceneRedo() { - spatial.setLocalTranslation(after); - RigidBodyControl control = spatial.getControl(RigidBodyControl.class); - if (control != null) { - control.setPhysicsLocation(spatial.getWorldTranslation()); - } - CharacterControl character = spatial.getControl(CharacterControl.class); - if (character != null) { - character.setPhysicsLocation(spatial.getWorldTranslation()); - } - //toolController.selectedSpatialTransformed(); - } - - public void setAfter(Vector3f after) { - this.after.set(after); - } - } -} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java index 43d3cba3a..87f3c283f 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java @@ -8,425 +8,51 @@ import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent; -import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; import com.jme3.gde.scenecomposer.SceneEditTool; -import com.jme3.input.KeyInput; -import com.jme3.input.event.KeyInputEvent; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.terrain.Terrain; import org.openide.loaders.DataObject; -import org.openide.util.Lookup; /** - * This duplicates the Blender manipulate tool. - * It supports quick access to Grab, Rotate, and Scale operations - * by typing one of the following keys: 'g', 'r', or 's' - * Those keys can be followed by an axis key to specify what axis - * to perform the transformation: x, y, z - * Then, after the operation and axis are selected, you can type in a - * number and then hit 'enter' to complete the transformation. - * - * Ctrl+Shift+D will duplicate an object - * X will delete an object - * - * ITEMS TO FINISH: - * 1) fixed scale and rotation values by holding Ctrl and dragging mouse - * BUGS: - * 1) window always needs focus from primary click when it should focus from secondary and middle mouse - * + * This duplicates the Blender manipulate tool. It supports quick access to + * Grab, Rotate, and Scale operations by typing one of the following keys: 'g', + * 'r', or 's' Those keys can be followed by an axis key to specify what axis to + * perform the transformation: x, y, z Then, after the operation and axis are + * selected, you can type in a number and then hit 'enter' to complete the + * transformation. + * + * Ctrl+Shift+D will duplicate an object X will delete an object + * + * ITEMS TO FINISH: 1) fixed scale and rotation values by holding Ctrl and + * dragging mouse BUGS: 1) window always needs focus from primary click when it + * should focus from secondary and middle mouse + * * @author Brent Owens */ public class SelectTool extends SceneEditTool { - private enum State { - - translate, rotate, scale - }; - private State currentState = null; - private Vector3f currentAxis = Vector3f.UNIT_XYZ; - private StringBuilder numberBuilder = new StringBuilder(); // gets appended with numbers - private Quaternion startRot; - private Vector3f startTrans; - private Vector3f startScale; - private boolean wasDraggingL = false; private boolean wasDraggingR = false; private boolean wasDownR = false; - private boolean ctrlDown = false; - private boolean shiftDown = false; - private boolean altDown = false; - private MoveManager.MoveUndo moving; - private ScaleUndo scaling; - private RotateUndo rotating; - private Vector2f startMouseCoord; // for scaling and rotation - private Vector2f startSelectedCoord; // for scaling and rotation - private float lastRotAngle; // used for rotation /** - * This is stateful: - * First it checks for a command (rotate, translate, delete, etc..) - * Then it checks for an axis (x,y,z) - * Then it checks for a number (user typed a number - * Then, finally, it checks if Enter was hit. - * + * This is stateful: First it checks for a command (rotate, translate, + * delete, etc..) Then it checks for an axis (x,y,z) Then it checks for a + * number (user typed a number Then, finally, it checks if Enter was hit. + * * If either of the commands was actioned, the preceeding states/axis/amount - * will be reset. For example if the user types: G Y 2 R - * Then it will: - * 1) Set state as 'Translate' for the G (grab) - * 2) Set the axis as 'Y'; it will translate along the Y axis - * 3) Distance will be 2, when the 2 key is hit - * 4) Distance, Axis, and state are then reset because a new state was set: Rotate - * it won't actually translate because 'Enter' was not hit and 'R' reset the state. - * + * will be reset. For example if the user types: G Y 2 R Then it will: 1) + * Set state as 'Translate' for the G (grab) 2) Set the axis as 'Y'; it will + * translate along the Y axis 3) Distance will be 2, when the 2 key is hit + * 4) Distance, Axis, and state are then reset because a new state was set: + * Rotate it won't actually translate because 'Enter' was not hit and 'R' + * reset the state. + * */ - @Override - public void keyPressed(KeyInputEvent kie) { - - checkModificatorKeys(kie); // alt,shift,ctrl - Spatial selected = toolController.getSelectedSpatial(); - - if (selected == null) { - return; // only do anything if a spatial is selected - } - // key released - if (kie.isPressed()) { - boolean commandUsed = checkCommandKey(kie); - boolean stateChange = checkStateKey(kie); - boolean axisChange = checkAxisKey(kie); - boolean numberChange = checkNumberKey(kie); - boolean enterHit = checkEnterHit(kie); - boolean escHit = checkEscHit(kie); - - if (commandUsed) { - return; // commands take priority - } - if (stateChange) { - currentAxis = Vector3f.UNIT_XYZ; - numberBuilder = new StringBuilder(); - recordInitialState(selected); - } else if (axisChange) { - } else if (numberChange) { - } else if (enterHit) { - if (currentState != null && numberBuilder.length() > 0) { - applyKeyedChangeState(selected); - clearState(false); - } - } - - - // ----------------------- - // reset conditions below: - - if (escHit) { - if (moving != null) { - moving.sceneUndo(); - } - - moving = null; - clearState(); - } - - if (!stateChange && !axisChange && !numberChange && !enterHit && !escHit) { - // nothing valid was hit, reset the state - //clearState(); // this will be - } - } - } - - /** - * Abort any manipulations - */ - private void clearState() { - clearState(true); - } - - private void clearState(boolean resetSelected) { - Spatial selected = toolController.getSelectedSpatial(); - if (resetSelected && selected != null) { - // reset the transforms - if (startRot != null) { - selected.setLocalRotation(startRot); - } - if (startTrans != null) { - selected.setLocalTranslation(startTrans); - } - if (startScale != null) { - selected.setLocalScale(startScale); - } - } - currentState = null; - currentAxis = Vector3f.UNIT_XYZ; - numberBuilder = new StringBuilder(); - startRot = null; - startTrans = null; - startScale = null; - startMouseCoord = null; - startSelectedCoord = null; - lastRotAngle = 0; - } - - private void recordInitialState(Spatial selected) { - startRot = selected.getLocalRotation().clone(); - startTrans = selected.getLocalTranslation().clone(); - startScale = selected.getLocalScale().clone(); - } - - /** - * Applies the changes entered by a number, not by mouse. - * Translate: adds the value to the current local translation - * Rotate: rotates by X degrees - * Scale: scale the current scale by X amount - */ - private void applyKeyedChangeState(Spatial selected) { - Float value = null; - try { - value = new Float(numberBuilder.toString()); - } catch (NumberFormatException e) { - return; - } - - if (currentState == State.translate) { - MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class); - moveManager.moveAcross(currentAxis, value, toolController.isSnapToGrid()); - moving.setAfter(selected.getLocalTranslation()); - actionPerformed(moving); - moving = null; - } else if (currentState == State.scale) { - float x = 1, y = 1, z = 1; - if (currentAxis == Vector3f.UNIT_X) { - x = value; - } else if (currentAxis == Vector3f.UNIT_Y) { - y = value; - } else if (currentAxis == Vector3f.UNIT_Z) { - z = value; - } else if (currentAxis == Vector3f.UNIT_XYZ) { - x = value; - y = value; - z = value; - } - Vector3f before = selected.getLocalScale().clone(); - Vector3f after = selected.getLocalScale().multLocal(x, y, z); - selected.setLocalScale(after); - actionPerformed(new ScaleUndo(selected, before, after)); - } else if (currentState == State.rotate) { - float x = 0, y = 0, z = 0; - if (currentAxis == Vector3f.UNIT_X) { - x = 1; - } else if (currentAxis == Vector3f.UNIT_Y) { - y = 1; - } else if (currentAxis == Vector3f.UNIT_Z) { - z = 1; - } - Vector3f axis = new Vector3f(x, y, z); - Quaternion initialRot = selected.getLocalRotation().clone(); - Quaternion rot = new Quaternion(); - rot = rot.fromAngleAxis(value * FastMath.DEG_TO_RAD, axis); - selected.setLocalRotation(selected.getLocalRotation().mult(rot)); - RotateUndo undo = new RotateUndo(selected, initialRot, rot); - actionPerformed(undo); - toolController.updateSelection(null);// force a re-draw of the bbox shape - toolController.updateSelection(selected); - - } - clearState(false); - } - - private void checkModificatorKeys(KeyInputEvent kie) { - if (kie.getKeyCode() == KeyInput.KEY_LCONTROL || kie.getKeyCode() == KeyInput.KEY_RCONTROL) { - ctrlDown = kie.isPressed(); - } - - if (kie.getKeyCode() == KeyInput.KEY_LSHIFT || kie.getKeyCode() == KeyInput.KEY_RSHIFT) { - shiftDown = kie.isPressed(); - } - - if (kie.getKeyCode() == KeyInput.KEY_LMENU || kie.getKeyCode() == KeyInput.KEY_RMENU) { - altDown = kie.isPressed(); - } - } - - private boolean checkCommandKey(KeyInputEvent kie) { - if (kie.getKeyCode() == KeyInput.KEY_D) { - if (shiftDown) { - duplicateSelected(); - return true; - } - } - // X will only delete if the user isn't already transforming - if (currentState == null && kie.getKeyCode() == KeyInput.KEY_X) { - if (!ctrlDown && !shiftDown) { - deleteSelected(); - return true; - } - } - return false; - } - - private boolean checkStateKey(KeyInputEvent kie) { - Spatial selected = toolController.getSelectedSpatial(); - if (kie.getKeyCode() == KeyInput.KEY_G && !ctrlDown) { - currentState = State.translate; - MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class); - moveManager.reset(); - Quaternion rot = camera.getRotation().mult(new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y)); - moveManager.initiateMove(selected, rot, false); - moving = moveManager.makeUndo(); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_R && !ctrlDown) { - currentState = State.rotate; - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_S && !ctrlDown) { - currentState = State.scale; - return true; - } - return false; - } - - private boolean checkAxisKey(KeyInputEvent kie) { - if (kie.getKeyCode() == KeyInput.KEY_X) { - currentAxis = Vector3f.UNIT_X; - checkMovePlane(MoveManager.XY, MoveManager.XZ); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_Y) { - currentAxis = Vector3f.UNIT_Y; - checkMovePlane(MoveManager.XY, MoveManager.YZ); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_Z) { - currentAxis = Vector3f.UNIT_Z; - checkMovePlane(MoveManager.XZ, MoveManager.YZ); - return true; - } - return false; - } - - private void checkMovePlane(Quaternion rot1, Quaternion rot2) { - if (currentState == State.translate) { - MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class); - Quaternion rot = camera.getRotation().mult(new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y)); - Quaternion planRot = null; - if (rot.dot(rot1) < rot.dot(rot2)) { - planRot = rot1; - } else { - planRot = rot2; - } - moveManager.updatePlaneRotation(planRot); - } - } - - private boolean checkNumberKey(KeyInputEvent kie) { - if (kie.getKeyCode() == KeyInput.KEY_MINUS) { - if (numberBuilder.length() > 0) { - if (numberBuilder.charAt(0) == '-') { - numberBuilder.replace(0, 1, ""); - } else { - numberBuilder.insert(0, '-'); - } - } else { - numberBuilder.append('-'); - } - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_0 || kie.getKeyCode() == KeyInput.KEY_NUMPAD0) { - numberBuilder.append('0'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_1 || kie.getKeyCode() == KeyInput.KEY_NUMPAD1) { - numberBuilder.append('1'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_2 || kie.getKeyCode() == KeyInput.KEY_NUMPAD2) { - numberBuilder.append('2'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_3 || kie.getKeyCode() == KeyInput.KEY_NUMPAD3) { - numberBuilder.append('3'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_4 || kie.getKeyCode() == KeyInput.KEY_NUMPAD4) { - numberBuilder.append('4'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_5 || kie.getKeyCode() == KeyInput.KEY_NUMPAD5) { - numberBuilder.append('5'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_6 || kie.getKeyCode() == KeyInput.KEY_NUMPAD6) { - numberBuilder.append('6'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_7 || kie.getKeyCode() == KeyInput.KEY_NUMPAD7) { - numberBuilder.append('7'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_8 || kie.getKeyCode() == KeyInput.KEY_NUMPAD8) { - numberBuilder.append('8'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_9 || kie.getKeyCode() == KeyInput.KEY_NUMPAD9) { - numberBuilder.append('9'); - return true; - } else if (kie.getKeyCode() == KeyInput.KEY_PERIOD) { - if (numberBuilder.indexOf(".") == -1) { // if it doesn't exist yet - if (numberBuilder.length() == 0 - || (numberBuilder.length() == 1 && numberBuilder.charAt(0) == '-')) { - numberBuilder.append("0."); - } else { - numberBuilder.append("."); - } - } - return true; - } - - return false; - } - - private boolean checkEnterHit(KeyInputEvent kie) { - if (kie.getKeyCode() == KeyInput.KEY_RETURN) { - return true; - } - return false; - } - - private boolean checkEscHit(KeyInputEvent kie) { - if (kie.getKeyCode() == KeyInput.KEY_ESCAPE) { - return true; - } - return false; - } - @Override public void actionPrimary(Vector2f screenCoord, boolean pressed, final JmeNode rootNode, DataObject dataObject) { - if (!pressed) { - Spatial selected = toolController.getSelectedSpatial(); - // left mouse released - if (!wasDraggingL) { - // left mouse pressed - if (currentState != null) { - // finish manipulating the spatial - if (moving != null) { - moving.setAfter(selected.getLocalTranslation()); - actionPerformed(moving); - moving = null; - clearState(false); - } else if (scaling != null) { - scaling.after = selected.getLocalScale().clone(); - actionPerformed(scaling); - scaling = null; - clearState(false); - toolController.rebuildSelectionBox(); - } else if (rotating != null) { - rotating.after = selected.getLocalRotation().clone(); - actionPerformed(rotating); - rotating = null; - clearState(false); - } - } else { - // mouse released and wasn't dragging, place cursor - final Vector3f result = pickWorldLocation(getCamera(), screenCoord, rootNode); - if (result != null) { - if (toolController.isSnapToGrid()) { - result.set(Math.round(result.x), result.y, Math.round(result.z)); - } - toolController.setCursorLocation(result); - } - } - } - wasDraggingL = false; - } + } @Override @@ -435,19 +61,7 @@ public class SelectTool extends SceneEditTool { Spatial selected = toolController.getSelectedSpatial(); // mouse down - if (moving != null) { - moving.sceneUndo(); - moving = null; - clearState(); - } else if (scaling != null) { - scaling.sceneUndo(); - scaling = null; - clearState(); - } else if (rotating != null) { - rotating.sceneUndo(); - rotating = null; - clearState(); - } else if (!wasDraggingR && !wasDownR) { // wasn't dragging and was not down already + if (!wasDraggingR && !wasDownR) { // wasn't dragging and was not down already // pick on the spot Spatial s = pickWorldSpatial(camera, screenCoord, rootNode); if (!toolController.selectTerrain() && isTerrain(s)) { @@ -498,8 +112,8 @@ public class SelectTool extends SceneEditTool { } /** - * Climb up the spatial until we find the first node parent. - * TODO: use userData to determine the actual model's parent. + * Climb up the spatial until we find the first node parent. TODO: use + * userData to determine the actual model's parent. */ private Spatial findModelNodeParent(Spatial child) { if (child == null) { @@ -519,14 +133,10 @@ public class SelectTool extends SceneEditTool { @Override public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) { - if (currentState != null) { - handleMouseManipulate(screenCoord, currentState, currentAxis, rootNode, currentDataObject, selectedSpatial); - } } @Override public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { - wasDraggingL = pressed; } @Override @@ -535,252 +145,8 @@ public class SelectTool extends SceneEditTool { } /** - * Manipulate the spatial - */ - private void handleMouseManipulate(Vector2f screenCoord, - State state, - Vector3f axis, - JmeNode rootNode, - DataObject currentDataObject, - JmeSpatial selectedSpatial) { - if (state == State.translate) { - doMouseTranslate(axis, screenCoord, rootNode, selectedSpatial); - } else if (state == State.scale) { - doMouseScale(axis, screenCoord, rootNode, selectedSpatial); - } else if (state == State.rotate) { - doMouseRotate(axis, screenCoord, rootNode, selectedSpatial); - } - - } - - private void doMouseTranslate(Vector3f axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) { - MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class); - if (toolController.isSnapToScene()) { - moveManager.setAlternativePickTarget(rootNode.getLookup().lookup(Node.class)); - } - // free form translation - moveManager.move(camera, screenCoord, axis, toolController.isSnapToGrid()); - } - - private void doMouseScale(Vector3f axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) { - Spatial selected = toolController.getSelectedSpatial(); - // scale based on the original mouse position and original model-to-screen position - // and compare that to the distance from the new mouse position and the original distance - if (startMouseCoord == null) { - startMouseCoord = screenCoord.clone(); - } - if (startSelectedCoord == null) { - Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation()); - startSelectedCoord = new Vector2f(screen.x, screen.y); - } - - if (scaling == null) { - scaling = new ScaleUndo(selected, selected.getLocalScale().clone(), null); - } - - float origDist = startMouseCoord.distanceSquared(startSelectedCoord); - float newDist = screenCoord.distanceSquared(startSelectedCoord); - if (origDist == 0) { - origDist = 1; - } - float ratio = newDist / origDist; - Vector3f prev = selected.getLocalScale(); - if (axis == Vector3f.UNIT_X) { - selected.setLocalScale(ratio, prev.y, prev.z); - } else if (axis == Vector3f.UNIT_Y) { - selected.setLocalScale(prev.x, ratio, prev.z); - } else if (axis == Vector3f.UNIT_Z) { - selected.setLocalScale(prev.x, prev.y, ratio); - } else { - selected.setLocalScale(ratio, ratio, ratio); - } - } - - private void doMouseRotate(Vector3f axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) { - Spatial selected = toolController.getSelectedSpatial(); - if (startMouseCoord == null) { - startMouseCoord = screenCoord.clone(); - } - if (startSelectedCoord == null) { - Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation()); - startSelectedCoord = new Vector2f(screen.x, screen.y); - } - - if (rotating == null) { - rotating = new RotateUndo(selected, selected.getLocalRotation().clone(), null); - } - - Vector2f origRot = startMouseCoord.subtract(startSelectedCoord); - Vector2f newRot = screenCoord.subtract(startSelectedCoord); - float newRotAngle = origRot.angleBetween(newRot); - float temp = newRotAngle; - - if (lastRotAngle != 0) { - newRotAngle -= lastRotAngle; - } - - lastRotAngle = temp; - - Quaternion rotate = new Quaternion(); - if (axis != Vector3f.UNIT_XYZ) { - rotate = rotate.fromAngleAxis(newRotAngle, selected.getWorldRotation().inverse().mult(axis)); - } else { - rotate = rotate.fromAngleAxis(newRotAngle, selected.getWorldRotation().inverse().mult(getCamera().getDirection().mult(-1).normalizeLocal())); - } - selected.setLocalRotation(selected.getLocalRotation().mult(rotate)); - - - } - - private void duplicateSelected() { - Spatial selected = toolController.getSelectedSpatial(); - if (selected == null) { - return; - } - Spatial clone = selected.clone(); - clone.move(1, 0, 1); - - selected.getParent().attachChild(clone); - actionPerformed(new DuplicateUndo(clone, selected.getParent())); - selected = clone; - final Spatial cloned = clone; - final JmeNode rootNode = toolController.getRootNode(); - refreshSelected(rootNode, selected.getParent()); - - java.awt.EventQueue.invokeLater(new Runnable() { - - @Override - public void run() { - if (cloned != null) { - SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)}); - SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(cloned)); - } - } - }); - - // set to automatically 'grab'/'translate' the new cloned model - toolController.updateSelection(selected); - currentState = State.translate; - currentAxis = Vector3f.UNIT_XYZ; - } - - private void deleteSelected() { - Spatial selected = toolController.getSelectedSpatial(); - if (selected == null) { - return; - } - Node parent = selected.getParent(); - selected.removeFromParent(); - actionPerformed(new DeleteUndo(selected, parent)); - - selected = null; - toolController.updateSelection(selected); - - final JmeNode rootNode = toolController.getRootNode(); - refreshSelected(rootNode, parent); - } - - private void refreshSelected(final JmeNode jmeRootNode, final Node parent) { - java.awt.EventQueue.invokeLater(new Runnable() { - - @Override - public void run() { - jmeRootNode.getChild(parent).refresh(false); - } - }); - } - - private class ScaleUndo extends AbstractUndoableSceneEdit { - - private Spatial spatial; - 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); - } - - @Override - public void sceneRedo() { - spatial.setLocalScale(after); - } - } - - private class RotateUndo extends AbstractUndoableSceneEdit { - - private Spatial spatial; - private Quaternion before, after; - - RotateUndo(Spatial spatial, Quaternion before, Quaternion after) { - this.spatial = spatial; - this.before = before; - this.after = after; - } - - @Override - public void sceneUndo() { - spatial.setLocalRotation(before); - } - - @Override - public void sceneRedo() { - spatial.setLocalRotation(after); - } - } - - private class DeleteUndo extends AbstractUndoableSceneEdit { - - private Spatial spatial; - private Node parent; - - DeleteUndo(Spatial spatial, Node parent) { - this.spatial = spatial; - this.parent = parent; - } - - @Override - public void sceneUndo() { - parent.attachChild(spatial); - } - - @Override - public void sceneRedo() { - spatial.removeFromParent(); - } - } - - private class DuplicateUndo extends AbstractUndoableSceneEdit { - - private Spatial spatial; - private Node parent; - - DuplicateUndo(Spatial spatial, Node parent) { - this.spatial = spatial; - this.parent = parent; - } - - @Override - public void sceneUndo() { - spatial.removeFromParent(); - } - - @Override - public void sceneRedo() { - parent.attachChild(spatial); - } - } - - /** - * Check if the selected item is a Terrain - * It will climb up the parent tree to see if - * a parent is terrain too. - * Recursive call. + * Check if the selected item is a Terrain It will climb up the parent tree + * to see if a parent is terrain too. Recursive call. */ protected boolean isTerrain(Spatial s) { if (s == null) { diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DeleteShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DeleteShortcut.java new file mode 100644 index 000000000..cc13ece1d --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DeleteShortcut.java @@ -0,0 +1,123 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.asset.AssetManager; +import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent; +import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; +import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; +import com.jme3.gde.scenecomposer.SceneComposerToolController; +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Vector2f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; + +/** + * + * @author dokthar + */ +public class DeleteShortcut extends ShortcutTool { + + @Override + public boolean isActivableBy(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_X && kie.isPressed()) { + if (Lookup.getDefault().lookup(ShortcutManager.class).isShiftDown()) { + return true; + } + } + return false; + } + + @Override + public void cancel() { + terminate(); + } + + @Override + public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { + super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates. + hideMarker(); + if (selectedSpatial != null) { + delete(); + } + terminate(); + } + + private void delete() { + Spatial selected = toolController.getSelectedSpatial(); + + Node parent = selected.getParent(); + selected.removeFromParent(); + actionPerformed(new DeleteUndo(selected, parent)); + + selected = null; + toolController.updateSelection(selected); + + final JmeNode rootNode = toolController.getRootNode(); + refreshSelected(rootNode, parent); + } + + private void refreshSelected(final JmeNode jmeRootNode, final Node parent) { + java.awt.EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + jmeRootNode.getChild(parent).refresh(false); + } + }); + } + + @Override + public void keyPressed(KeyInputEvent kie) { + + } + + @Override + public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + } + + @Override + public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + } + + @Override + public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) { + } + + @Override + public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } + + private class DeleteUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + private Node parent; + + DeleteUndo(Spatial spatial, Node parent) { + this.spatial = spatial; + this.parent = parent; + } + + @Override + public void sceneUndo() { + parent.attachChild(spatial); + } + + @Override + public void sceneRedo() { + spatial.removeFromParent(); + } + } +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java new file mode 100644 index 000000000..d98eb6ee9 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java @@ -0,0 +1,141 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.gde.scenecomposer.tools.shortcuts; + +import com.jme3.asset.AssetManager; +import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent; +import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; +import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; +import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent; +import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit; +import com.jme3.gde.scenecomposer.SceneComposerToolController; +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.math.Vector2f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; + +/** + * + * @author dokthar + */ +public class DuplicateShortcut extends ShortcutTool { + + @Override + public boolean isActivableBy(KeyInputEvent kie) { + if (kie.getKeyCode() == KeyInput.KEY_D && kie.isPressed()) { + if (Lookup.getDefault().lookup(ShortcutManager.class).isShiftDown()) { + return true; + } + } + return false; + } + + @Override + public void cancel() { + terminate(); + } + + @Override + public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) { + super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates. + hideMarker(); + if (selectedSpatial != null) { + duplicate(); + terminate(); + + //then enable move shortcut + toolController.doKeyPressed(new KeyInputEvent(KeyInput.KEY_G, 'g', true, false)); + } else { + terminate(); + } + } + + private void duplicate() { + Spatial selected = toolController.getSelectedSpatial(); + + Spatial clone = selected.clone(); + clone.move(1, 0, 1); + + selected.getParent().attachChild(clone); + actionPerformed(new DuplicateUndo(clone, selected.getParent())); + selected = clone; + final Spatial cloned = clone; + final JmeNode rootNode = toolController.getRootNode(); + refreshSelected(rootNode, selected.getParent()); + + java.awt.EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + if (cloned != null) { + SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)}); + SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(cloned)); + } + } + }); + + toolController.updateSelection(selected); + } + + private void refreshSelected(final JmeNode jmeRootNode, final Node parent) { + java.awt.EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + jmeRootNode.getChild(parent).refresh(false); + } + }); + } + + @Override + public void keyPressed(KeyInputEvent kie) { + + } + + @Override + public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + } + + @Override + public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) { + } + + @Override + public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) { + } + + @Override + public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } + + @Override + public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { + } + + private class DuplicateUndo extends AbstractUndoableSceneEdit { + + private Spatial spatial; + private Node parent; + + DuplicateUndo(Spatial spatial, Node parent) { + this.spatial = spatial; + this.parent = parent; + } + + @Override + public void sceneUndo() { + spatial.removeFromParent(); + } + + @Override + public void sceneRedo() { + parent.attachChild(spatial); + } + } +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java index bdbcfbe9e..512e36e0c 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java @@ -22,12 +22,17 @@ public class ShortcutManager { private ShortcutTool currentShortcut; private ArrayList shortcutList; + private boolean ctrlDown = false; + private boolean shiftDown = false; + private boolean altDown = false; public ShortcutManager() { shortcutList = new ArrayList(); shortcutList.add(new MoveShortcut()); shortcutList.add(new RotateShortcut()); shortcutList.add(new ScaleShortcut()); + shortcutList.add(new DuplicateShortcut()); + shortcutList.add(new DeleteShortcut()); } /* @@ -41,6 +46,27 @@ public class ShortcutManager { return currentShortcut != null; } + /** + * @return the ctrlDown + */ + public boolean isCtrlDown() { + return ctrlDown; + } + + /** + * @return the shiftDown + */ + public boolean isShiftDown() { + return shiftDown; + } + + /** + * @return the altDown + */ + public boolean isAltDown() { + return altDown; + } + public void setShortCut(ShortcutTool shortcut) { if (isActive()) { currentShortcut.cancel(); @@ -49,6 +75,9 @@ public class ShortcutManager { } public ShortcutTool getActivableShortcut(KeyInputEvent kie) { + if (checkCommandeKey(kie)) { + return null; + } for (ShortcutTool s : shortcutList) { if (s != currentShortcut) { if (s.isActivableBy(kie)) { @@ -69,12 +98,12 @@ public class ShortcutManager { public boolean activateShortcut(KeyInputEvent kie) { ShortcutTool newShortcut = getActivableShortcut(kie); - if(newShortcut != null){ + if (newShortcut != null) { currentShortcut = newShortcut; } return newShortcut != null; } - + /** * This should be called to trigger the currentShortcut.keyPressed() method. * This method do a first check for command key used to provide isCtrlDown, @@ -83,12 +112,27 @@ public class ShortcutManager { * @param kie */ public void doKeyPressed(KeyInputEvent kie) { - ///todo check commande key - if (isActive()) { + if (checkCommandeKey(kie)) { + //return; + } else if (isActive()) { currentShortcut.keyPressed(kie); } } + private boolean checkCommandeKey(KeyInputEvent kie) { + if (checkCtrlHit(kie)) { + ctrlDown = kie.isPressed(); + return true; + } else if (checkAltHit(kie)) { + altDown = kie.isPressed(); + return true; + } else if (checkShiftHit(kie)) { + shiftDown = kie.isPressed(); + return true; + } + return false; + } + /* STATIC */ From 97e1cf8cf3344fb1cbcb8c477f1a486386692377 Mon Sep 17 00:00:00 2001 From: Maselbas Date: Fri, 15 May 2015 22:38:18 +0200 Subject: [PATCH 188/225] SDK SceneComposer : added comments in the ShortCutManager - refactor getNumberkey() to getNumberKey() --- .../tools/shortcuts/MoveShortcut.java | 2 +- .../tools/shortcuts/RotateShortcut.java | 2 +- .../tools/shortcuts/ScaleShortcut.java | 2 +- .../tools/shortcuts/ShortcutManager.java | 90 ++++++++++++++++++- 4 files changed, 89 insertions(+), 7 deletions(-) diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java index 62cc70612..6291ab932 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java @@ -103,7 +103,7 @@ public class MoveShortcut extends ShortcutTool { } } else if (axisChanged || numberChanged) { //update transformation - float number = ShortcutManager.getNumberkey(numberBuilder); + float number = ShortcutManager.getNumberKey(numberBuilder); Vector3f translation = currentAxis.mult(number); finalPosition = startPosition.add(translation); spatial.setLocalTranslation(finalPosition); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java index 1fb445b59..8b9ffe404 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java @@ -95,7 +95,7 @@ public class RotateShortcut extends ShortcutTool { spatial.setLocalRotation(startRotation.clone()); } else if (axisChanged || numberChanged) { //update transformation - /* float number = ShortcutManager.getNumberkey(numberBuilder); + /* float number = ShortcutManager.getNumberKey(numberBuilder); Vector3f translation = currentAxis.mult(number); finalPosition = startPosition.add(translation); spatial.setLocalTranslation(finalPosition); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java index 21966e385..8fa18858f 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java @@ -94,7 +94,7 @@ public class ScaleShortcut extends ShortcutTool { pickEnabled = false; } else if (axisChanged || numberChanged) { //update transformation - /* float number = ShortcutManager.getNumberkey(numberBuilder); + /* float number = ShortcutManager.getNumberKey(numberBuilder); Vector3f translation = currentAxis.mult(number); finalPosition = startPosition.add(translation); spatial.setLocalTranslation(finalPosition); diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java index 512e36e0c..187293e92 100644 --- a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java @@ -38,10 +38,17 @@ public class ShortcutManager { /* Methodes */ + /** + * This MUST be called by the shortcut tool once the modifications are done. + */ public void terminate() { currentShortcut = null; } + /** + * + * @return true if a shortCutTool is active, else return false. + */ public boolean isActive() { return currentShortcut != null; } @@ -67,6 +74,12 @@ public class ShortcutManager { return altDown; } + /** + * Set the current shortcut to shortcut. cancel the current + * shortcut if it was still active + * + * @param shortcut the ShortCutTool to set + */ public void setShortCut(ShortcutTool shortcut) { if (isActive()) { currentShortcut.cancel(); @@ -74,10 +87,18 @@ public class ShortcutManager { currentShortcut = shortcut; } + /** + * Get the shortcut that can be enable with the given kei, the current + * shortcut cannot be enable twice. This also check for command key used to + * provide isCtrlDown(), isShiftDown() and isAltDown(). + * + * @param kie the KeyInputEvent + * @return the activable shortcut else return null + */ public ShortcutTool getActivableShortcut(KeyInputEvent kie) { if (checkCommandeKey(kie)) { return null; - } + } for (ShortcutTool s : shortcutList) { if (s != currentShortcut) { if (s.isActivableBy(kie)) { @@ -88,14 +109,30 @@ public class ShortcutManager { return null; } + /** + * + * @return the current active shortcut + */ public ShortcutTool getActiveShortcut() { return currentShortcut; } + /** + * + * @param kie the KeyInputEvent + * @return true if the given Kei can enable a sortcut, else false + */ public boolean canActivateShortcut(KeyInputEvent kie) { return getActivableShortcut(kie) != null; } + /** + * Set the current shortcut with the shortcut one that can be enable with + * the given key + * + * @param kie the KeyInputEvent + * @return true is the shortcut changed, else false + */ public boolean activateShortcut(KeyInputEvent kie) { ShortcutTool newShortcut = getActivableShortcut(kie); if (newShortcut != null) { @@ -106,8 +143,8 @@ public class ShortcutManager { /** * This should be called to trigger the currentShortcut.keyPressed() method. - * This method do a first check for command key used to provide isCtrlDown, - * isShiftDown ect.. to de + * This also check for command key used to provide isCtrlDown(), + * isShiftDown() and isAltDown(). * * @param kie */ @@ -136,6 +173,11 @@ public class ShortcutManager { /* STATIC */ + /** + * + * @param kie + * @return true if the given kie is KEY_RETURN + */ public static boolean checkEnterHit(KeyInputEvent kie) { if (kie.getKeyCode() == KeyInput.KEY_RETURN) { return true; @@ -143,6 +185,11 @@ public class ShortcutManager { return false; } + /** + * + * @param kie + * @return true if the given kie is KEY_ESCAPE + */ public static boolean checkEscHit(KeyInputEvent kie) { if (kie.getKeyCode() == KeyInput.KEY_ESCAPE) { return true; @@ -150,6 +197,11 @@ public class ShortcutManager { return false; } + /** + * + * @param kie + * @return true if the given kie is KEY_LCONTROL || KEY_RCONTROL + */ public static boolean checkCtrlHit(KeyInputEvent kie) { if (kie.getKeyCode() == KeyInput.KEY_LCONTROL || kie.getKeyCode() == KeyInput.KEY_RCONTROL) { return true; @@ -157,6 +209,11 @@ public class ShortcutManager { return false; } + /** + * + * @param kie + * @return true if the given kie is KEY_LSHIFT || KEY_RSHIFT + */ public static boolean checkShiftHit(KeyInputEvent kie) { if (kie.getKeyCode() == KeyInput.KEY_LSHIFT || kie.getKeyCode() == KeyInput.KEY_RSHIFT) { return true; @@ -164,6 +221,11 @@ public class ShortcutManager { return false; } + /** + * + * @param kie + * @return true if the given kie is KEY_LMENU || KEY_RMENU + */ public static boolean checkAltHit(KeyInputEvent kie) { if (kie.getKeyCode() == KeyInput.KEY_LMENU || kie.getKeyCode() == KeyInput.KEY_RMENU) { return true; @@ -171,6 +233,13 @@ public class ShortcutManager { return false; } + /** + * store the number kie into the numberBuilder + * + * @param kie + * @param numberBuilder + * @return true if the given kie is handled as a number key event + */ public static boolean checkNumberKey(KeyInputEvent kie, StringBuilder numberBuilder) { if (kie.getKeyCode() == KeyInput.KEY_MINUS) { if (numberBuilder.length() > 0) { @@ -228,7 +297,12 @@ public class ShortcutManager { return false; } - public static float getNumberkey(StringBuilder numberBuilder) { + /** + * + * @param numberBuilder the StringBuilder storing the float number + * @return the float value created from the given StringBuilder + */ + public static float getNumberKey(StringBuilder numberBuilder) { if (numberBuilder.length() == 0) { return 0; } else { @@ -236,6 +310,14 @@ public class ShortcutManager { } } + /** + * Check for axis input for key X,Y,Z and store the corresponding UNIT_ into + * the axisStore + * + * @param kie + * @param axisStore + * @return true if the given kie is handled as a Axis input + */ public static boolean checkAxisKey(KeyInputEvent kie, Vector3f axisStore) { if (kie.getKeyCode() == KeyInput.KEY_X) { axisStore.set(Vector3f.UNIT_X); From afc8f3f1cd9a2895dba0884711a4ca6c3204a5a1 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 22 May 2015 12:51:09 +0200 Subject: [PATCH 189/225] Fixed SpotLight default inititalisation for invSpotRange --- jme3-core/src/main/java/com/jme3/light/SpotLight.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index f02f76fa4..e6443df7c 100644 --- a/jme3-core/src/main/java/com/jme3/light/SpotLight.java +++ b/jme3-core/src/main/java/com/jme3/light/SpotLight.java @@ -63,7 +63,7 @@ public class SpotLight extends Light { protected float spotInnerAngle = FastMath.QUARTER_PI / 8; protected float spotOuterAngle = FastMath.QUARTER_PI / 6; protected float spotRange = 100; - protected float invSpotRange = 1 / 100; + protected float invSpotRange = 1f / 100; protected float packedAngleCos=0; protected float outerAngleCosSqr, outerAngleSinSqr; From 46e4c21c2c9b19cba337b8c3f1b7ffc12ca2b871 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Sun, 24 May 2015 16:09:41 +0200 Subject: [PATCH 190/225] Added: GL_MAX_VERTEX_UNIFORM_COMPONENTS --- jme3-core/src/main/java/com/jme3/renderer/Limits.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/renderer/Limits.java b/jme3-core/src/main/java/com/jme3/renderer/Limits.java index b10c539b8..7df9bc76b 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Limits.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Limits.java @@ -75,4 +75,6 @@ public enum Limits { ColorTextureSamples, DepthTextureSamples, + + VertexUniformComponents, } From 14e4f2bfb3d986806bfd7a12e092a8bfd6d31f87 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Sun, 24 May 2015 16:12:28 +0200 Subject: [PATCH 191/225] Added: GL_MAX_VERTEX_UNIFORM_COMPONENTS --- jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java | 1 + .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) 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 76eedb521..21eedab52 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 @@ -99,6 +99,7 @@ public interface GL { public static final int GL_MAX_TEXTURE_SIZE = 0xD33; public static final int GL_MAX_VERTEX_ATTRIBS = 0x8869; public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; + public static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A; public static final int GL_MIRRORED_REPEAT = 0x8370; public static final int GL_NEAREST = 0x2600; public static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702; 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 3f14bdb10..caf472543 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 @@ -196,7 +196,7 @@ public class GLRenderer implements Renderer { } int glslVer = extractVersion(gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)); - + switch (glslVer) { default: if (glslVer < 400) { @@ -249,7 +249,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); @@ -257,7 +257,7 @@ public class GLRenderer implements Renderer { // gl.glGetInteger(GL.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); // fragUniforms = intBuf16.get(0); // logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - + limits.put(Limits.VertexUniformComponents,getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS)); limits.put(Limits.VertexAttributes, getInteger(GL.GL_MAX_VERTEX_ATTRIBS)); limits.put(Limits.TextureSize, getInteger(GL.GL_MAX_TEXTURE_SIZE)); limits.put(Limits.CubemapSize, getInteger(GL.GL_MAX_CUBE_MAP_TEXTURE_SIZE)); From 4bd774a6539656166ae815bdb147a6cff7b17910 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Thu, 28 May 2015 01:10:44 -0400 Subject: [PATCH 192/225] Created an abstract hosted connection service that has the autohost, start/stopHostingOnConnection support built into it. This is a very common things for connection based services and I got tired of cutting/pasting it all the time. RpcHostedService was modified to extend this base class instead of the more basic one. --- .../AbstractHostedConnectionService.java | 148 ++++++++++++++++++ .../network/service/rpc/RpcHostedService.java | 62 +------- 2 files changed, 153 insertions(+), 57 deletions(-) create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java new file mode 100644 index 000000000..485712b95 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java @@ -0,0 +1,148 @@ +/* + * 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; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Convenient base class for HostedServices providing some default HostedService + * interface implementations as well as a few convenience methods + * such as getServiceManager() and getService(type). This implementation + * enhances the default capabilities provided by AbstractHostedService by + * adding automatic connection management. + * + *

    Subclasses must at least override the onInitialize(), startHostingOnConnection(), and + * stopHostingOnConnection() methods to handle service and connection initialization.

    + * + *

    An autoHost flag controls whether startHostingOnConnection() is called + * automatically when new connections are detected. If autoHohst is false then it + * is up to the implementation or appliction to specifically start hosting at + * some point.

    + * + * @author Paul Speed + */ +public abstract class AbstractHostedConnectionService extends AbstractHostedService { + + static final Logger log = Logger.getLogger(AbstractHostedConnectionService.class.getName()); + + private boolean autoHost; + + /** + * Creates a new HostedService that will autohost connections + * when detected. + */ + protected AbstractHostedConnectionService() { + this(true); + } + + /** + * Creates a new HostedService that will automatically host + * connections only if autoHost is true. + */ + protected AbstractHostedConnectionService( boolean autoHost ) { + this.autoHost = autoHost; + } + + /** + * When set to true, all new connections will automatically have + * hosting services attached to them by calling startHostingOnConnection(). + * If this is set to false then it is up to the application or 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 service-related calls until + * it has provided more information... for example, logging in.

    + */ + public void setAutoHost( boolean b ) { + this.autoHost = b; + } + + /** + * Returns true if this service automatically attaches + * hosting capabilities to new connections. + */ + public boolean getAutoHost() { + return autoHost; + } + + + /** + * Performs implementation specific connection hosting setup. + * Generally this involves setting up some handlers or session + * attributes on the connection. If autoHost is true then this + * method is called automatically during connectionAdded() + * processing. + */ + public abstract void startHostingOnConnection( HostedConnection hc ); + + /** + * Performs implementation specific connection tear-down. + * This will be called automatically when the connectionRemoved() + * event occurs... whether the application has already called it + * or not. + */ + public abstract void stopHostingOnConnection( HostedConnection hc ); + + /** + * 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. This method always calls stopHostingOnConnection(hc). + * Implementations should be aware that if they stopHostingOnConnection() + * early that they will get a second call when the connection goes away. + */ + @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/RpcHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java index 4a347b5a3..2d6f28593 100644 --- 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 @@ -35,8 +35,8 @@ 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.service.AbstractHostedConnectionService; 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; @@ -62,13 +62,12 @@ import java.util.logging.Logger; * * @author Paul Speed */ -public class RpcHostedService extends AbstractHostedService { +public class RpcHostedService extends AbstractHostedConnectionService { private static final String ATTRIBUTE_NAME = "rpcSession"; static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); - private boolean autoHost; private SessionDataDelegator delegator; /** @@ -87,7 +86,7 @@ public class RpcHostedService extends AbstractHostedService { * on the specified 'autoHost' flag. */ public RpcHostedService( boolean autoHost ) { - this.autoHost = autoHost; + super(autoHost); // This works for me... has to be different in // the general case @@ -115,31 +114,6 @@ public class RpcHostedService extends AbstractHostedService { } } - /** - * 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 @@ -157,6 +131,7 @@ public class RpcHostedService extends AbstractHostedService { * This method is called automatically for all new connections if * autohost is set to true. */ + @Override public void startHostingOnConnection( HostedConnection hc ) { if( log.isLoggable(Level.FINEST) ) { log.log(Level.FINEST, "startHostingOnConnection:{0}", hc); @@ -173,6 +148,7 @@ public class RpcHostedService extends AbstractHostedService { * This method is called automatically for all leaving connections if * autohost is set to true. */ + @Override public void stopHostingOnConnection( HostedConnection hc ) { RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME); if( rpc == null ) { @@ -195,33 +171,5 @@ public class RpcHostedService extends AbstractHostedService { 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); - } - } From ceb45ff718780975bf8e253bd98100e951f73ba4 Mon Sep 17 00:00:00 2001 From: Normen Hansen Date: Sun, 31 May 2015 15:18:59 +0200 Subject: [PATCH 193/225] SDK: - close asset after loading with ModelImportTool --- .../src/com/jme3/gde/modelimporter/ImportModel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ImportModel.java b/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ImportModel.java index 1f3f57bf8..07b129628 100644 --- a/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ImportModel.java +++ b/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ImportModel.java @@ -212,6 +212,7 @@ public final class ImportModel implements ActionListener { } replaceLocatedTextures(spat, manager); targetData.saveAsset(); + targetData.closeAsset(); ((SpatialAssetDataObject) targetModel).getLookupContents().remove(tempProjectManager); } } catch (Exception ex) { From 6f33002f9ac5e9ba2401ec4212f165f0adf9d558 Mon Sep 17 00:00:00 2001 From: Normen Hansen Date: Sun, 31 May 2015 16:06:22 +0200 Subject: [PATCH 194/225] SDK: - close asset after loading with ModelImportTool --- .../com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java b/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java index f7eeb57fa..51c0578e3 100644 --- a/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java +++ b/sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java @@ -126,6 +126,7 @@ public final class ModelImporterVisualPanel3 extends JPanel { DialogDisplayer.getDefault().notifyLater(msg); Exceptions.printStackTrace(e); } + data.closeAsset(); manager.unregisterLocator(manager.getAssetFolderName(), UberAssetLocator.class); panel.fireChangeEvent(); } From e2d8fe829359dd6028d6810efddbf671d9980231 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 31 May 2015 16:00:55 -0400 Subject: [PATCH 195/225] GLRenderer: support luminance / alpha textures in core profile --- .../java/com/jme3/renderer/opengl/GL.java | 2 + .../java/com/jme3/renderer/opengl/GL3.java | 13 ++++++- .../java/com/jme3/renderer/opengl/GL4.java | 2 +- .../jme3/renderer/opengl/GLImageFormat.java | 21 ++++++++++ .../jme3/renderer/opengl/GLImageFormats.java | 28 ++++++++++++++ .../com/jme3/renderer/opengl/GLRenderer.java | 2 +- .../com/jme3/renderer/opengl/TextureUtil.java | 38 +++++++++++++++++-- 7 files changed, 99 insertions(+), 7 deletions(-) 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 76eedb521..12f14c8aa 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 @@ -74,6 +74,7 @@ public interface GL { public static final int GL_FRONT_AND_BACK = 0x408; public static final int GL_GEQUAL = 0x206; public static final int GL_GREATER = 0x204; + public static final int GL_GREEN = 0x1904; public static final int GL_INCR = 0x1E02; public static final int GL_INCR_WRAP = 0x8507; public static final int GL_INFO_LOG_LENGTH = 0x8B84; @@ -114,6 +115,7 @@ public interface GL { public static final int GL_OUT_OF_MEMORY = 0x505; public static final int GL_POINTS = 0x0; public static final int GL_POLYGON_OFFSET_FILL = 0x8037; + public static final int GL_RED = 0x1903; public static final int GL_RENDERER = 0x1F01; public static final int GL_REPEAT = 0x2901; public static final int GL_REPLACE = 0x1E01; 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 190ed4547..2a7c38bb9 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 @@ -34,7 +34,7 @@ package com.jme3.renderer.opengl; import java.nio.IntBuffer; /** - * GL functions only available on vanilla desktop OpenGL 3.0. + * GL functions only available on vanilla desktop OpenGL 3.0+. * * @author Kirill Vainer */ @@ -43,6 +43,17 @@ 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_NUM_EXTENSIONS = 0x821D; + public static final int GL_R8 = 0x8229; + public static final int GL_R16F = 0x822D; + public static final int GL_R32F = 0x822E; + public static final int GL_RG16F = 0x822F; + public static final int GL_RG32F = 0x8230; + public static final int GL_RG = 0x8227; + public static final int GL_RG8 = 0x822B; + public static final int GL_TEXTURE_SWIZZLE_A = 0x8E45; + public static final int GL_TEXTURE_SWIZZLE_B = 0x8E44; + public static final int GL_TEXTURE_SWIZZLE_G = 0x8E43; + public static final int GL_TEXTURE_SWIZZLE_R = 0x8E42; public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+ public void glBindVertexArray(int param1); /// GL3+ diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java index ce982eebf..058bc00ec 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java @@ -34,7 +34,7 @@ package com.jme3.renderer.opengl; import java.nio.IntBuffer; /** - * GL functions only available on vanilla desktop OpenGL 3.0. + * GL functions only available on vanilla desktop OpenGL 4.0. * * @author Kirill Vainer */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java index 71d95f59b..8e65fbdef 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java @@ -42,6 +42,7 @@ public final class GLImageFormat { public final int format; public final int dataType; public final boolean compressed; + public final boolean swizzleRequired; /** * Constructor for formats. @@ -55,6 +56,7 @@ public final class GLImageFormat { this.format = format; this.dataType = dataType; this.compressed = false; + this.swizzleRequired = false; } /** @@ -63,11 +65,30 @@ public final class GLImageFormat { * @param internalFormat OpenGL internal format * @param format OpenGL format * @param dataType OpenGL datatype + * @param compressed Format is compressed */ public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { this.internalFormat = internalFormat; this.format = format; this.dataType = dataType; this.compressed = compressed; + this.swizzleRequired = false; + } + + /** + * Constructor for formats. + * + * @param internalFormat OpenGL internal format + * @param format OpenGL format + * @param dataType OpenGL datatype + * @param compressed Format is compressed + * @param swizzleRequired Need to use texture swizzle to upload texture + */ + public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed, boolean swizzleRequired) { + this.internalFormat = internalFormat; + this.format = format; + this.dataType = dataType; + this.compressed = compressed; + this.swizzleRequired = swizzleRequired; } } 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 a7ef9f52d..ab80b9e2a 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 @@ -52,6 +52,13 @@ public final class GLImageFormats { formatToGL[0][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType); } + private static void formatSwiz(GLImageFormat[][] formatToGL, Image.Format format, + int glInternalFormat, + int glFormat, + int glDataType){ + formatToGL[0][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, false, true); + } + private static void formatSrgb(GLImageFormat[][] formatToGL, Image.Format format, int glInternalFormat, int glFormat, @@ -60,6 +67,14 @@ public final class GLImageFormats { formatToGL[1][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType); } + private static void formatSrgbSwiz(GLImageFormat[][] formatToGL, Image.Format format, + int glInternalFormat, + int glFormat, + int glDataType) + { + formatToGL[1][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, false, true); + } + private static void formatComp(GLImageFormat[][] formatToGL, Image.Format format, int glCompressedFormat, int glFormat, @@ -88,6 +103,19 @@ public final class GLImageFormats { public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length]; + // Core Profile Formats (supported by both OpenGL Core 3.3 and OpenGL ES 3.0+) + if (caps.contains(Caps.CoreProfile)) { + formatSwiz(formatToGL, Format.Alpha8, GL3.GL_R8, GL.GL_RED, GL.GL_UNSIGNED_BYTE); + formatSwiz(formatToGL, Format.Luminance8, GL3.GL_R8, GL.GL_RED, GL.GL_UNSIGNED_BYTE); + formatSwiz(formatToGL, Format.Luminance8Alpha8, GL3.GL_RG8, GL3.GL_RG, GL.GL_UNSIGNED_BYTE); + formatSwiz(formatToGL, Format.Luminance16F, GL3.GL_R16F, GL.GL_RED, GLExt.GL_HALF_FLOAT_ARB); + formatSwiz(formatToGL, Format.Luminance32F, GL3.GL_R32F, GL.GL_RED, GL.GL_FLOAT); + formatSwiz(formatToGL, Format.Luminance16FAlpha16F, GL3.GL_RG16F, GL3.GL_RG, GLExt.GL_HALF_FLOAT_ARB); + + formatSrgbSwiz(formatToGL, Format.Luminance8, GLExt.GL_SRGB8_EXT, GL.GL_RED, GL.GL_UNSIGNED_BYTE); + formatSrgbSwiz(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SRGB8_ALPHA8_EXT, GL3.GL_RG, GL.GL_UNSIGNED_BYTE); + } + if (caps.contains(Caps.OpenGL20)) { if (!caps.contains(Caps.CoreProfile)) { format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); 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 3f14bdb10..23527d1ab 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 @@ -106,7 +106,7 @@ public class GLRenderer implements Renderer { this.gl4 = gl instanceof GL4 ? (GL4)gl : null; this.glfbo = glfbo; this.glext = glext; - this.texUtil = new TextureUtil(gl, gl2, glext, context); + this.texUtil = new TextureUtil(gl, gl2, glext); } @Override diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java index 37ffe5ed3..a2f21ceaa 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java @@ -54,14 +54,12 @@ final class TextureUtil { private final GL gl; private final GL2 gl2; private final GLExt glext; - private final RenderContext context; private GLImageFormat[][] formats; - - public TextureUtil(GL gl, GL2 gl2, GLExt glext, RenderContext context) { + + public TextureUtil(GL gl, GL2 gl2, GLExt glext) { this.gl = gl; this.gl2 = gl2; this.glext = glext; - this.context = context; } public void initialize(EnumSet caps) { @@ -103,6 +101,33 @@ final class TextureUtil { return glFmt; } + private void setupTextureSwizzle(int target, Format format) { + // Needed for OpenGL 3.3 to support luminance / alpha formats + switch (format) { + case Alpha8: + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_ZERO); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_ZERO); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_ZERO); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_RED); + break; + case Luminance8: + case Luminance16F: + case Luminance32F: + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_ONE); + break; + case Luminance8Alpha8: + case Luminance16FAlpha16F: + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_GREEN); + break; + } + } + private void uploadTextureLevel(GLImageFormat format, int target, int level, int slice, int sliceCount, int width, int height, int depth, int samples, ByteBuffer data) { if (format.compressed && data != null) { if (target == GL2.GL_TEXTURE_3D) { @@ -242,6 +267,11 @@ final class TextureUtil { } int samples = image.getMultiSamples(); + + // For OGL3 core: setup texture swizzle. + if (oglFormat.swizzleRequired) { + setupTextureSwizzle(target, jmeFormat); + } for (int i = 0; i < mipSizes.length; i++) { int mipWidth = Math.max(1, width >> i); From 7fa735fdb1d15c36171bf9044dc2bc8aa45d97d4 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 18 Jun 2015 16:26:36 +0200 Subject: [PATCH 196/225] Remove root slash from the asset path --- .../src/plugins/java/com/jme3/asset/plugins/ZipLocator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java index d20816f34..22e7cc244 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java @@ -82,6 +82,7 @@ public class ZipLocator implements AssetLocator { public AssetInfo locate(AssetManager manager, AssetKey key) { String name = key.getName(); + if(name.startsWith("/"))name=name.substring(1); ZipEntry entry = zipfile.getEntry(name); if (entry == null) return null; From f7a4fe9f2513ac0fa5f78b7aca12606efaa75da5 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Thu, 18 Jun 2015 23:51:17 +0200 Subject: [PATCH 197/225] Improvement of the Inverse Kinematics Constraint. --- jme3-blender/build.gradle | 5 +- .../blender/constraints/Constraint.java | 4 +- .../definitions/ConstraintDefinition.java | 6 + .../ConstraintDefinitionFactory.java | 12 +- .../definitions/ConstraintDefinitionIK.java | 268 +++++++++--------- .../plugins/blender/math/DQuaternion.java | 128 +++++++++ .../plugins/blender/math/DTransform.java | 26 +- .../scene/plugins/blender/math/Matrix.java | 214 ++++++++++++++ 8 files changed, 510 insertions(+), 153 deletions(-) create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java diff --git a/jme3-blender/build.gradle b/jme3-blender/build.gradle index 1b42c7109..a556d7a65 100644 --- a/jme3-blender/build.gradle +++ b/jme3-blender/build.gradle @@ -6,4 +6,7 @@ dependencies { compile project(':jme3-core') compile project(':jme3-desktop') compile project(':jme3-effects') -} + compile ('org.ejml:core:0.27') + compile ('org.ejml:dense64:0.27') + compile ('org.ejml:simple:0.27') +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java index 91cfd2f3f..f2268199c 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java @@ -64,7 +64,7 @@ public abstract class Constraint { Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); if (pData.isNotNull()) { Structure data = pData.fetchData().get(0); - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, ownerOMA, blenderContext); + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, name, ownerOMA, blenderContext); Pointer pTar = (Pointer) data.getFieldValue("tar"); if (pTar != null && pTar.isNotNull()) { targetOMA = pTar.getOldMemoryAddress(); @@ -77,7 +77,7 @@ public abstract class Constraint { } } else { // Null constraint has no data, so create it here - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, null, blenderContext); + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, name, null, blenderContext); } ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); ipo = influenceIpo; diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java index 346554052..a1f3a7316 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java @@ -30,6 +30,8 @@ public abstract class ConstraintDefinition { protected Set alteredOmas; /** The variable that determines if the constraint will alter the track in any way. */ protected boolean trackToBeChanged = true; + /** The name of the constraint. */ + protected String constraintName; /** * Loads a constraint definition based on the constraint definition @@ -53,6 +55,10 @@ public abstract class ConstraintDefinition { constraintHelper = (ConstraintHelper) (blenderContext == null ? null : blenderContext.getHelper(ConstraintHelper.class)); this.ownerOMA = ownerOMA; } + + public void setConstraintName(String constraintName) { + this.constraintName = constraintName; + } /** * @return determines if the definition of the constraint will change the bone in any way; in most cases diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java index cbf727290..c1d69fe06 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java @@ -92,7 +92,7 @@ public class ConstraintDefinitionFactory { * this exception is thrown when the blender file is somehow * corrupted */ - public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException { + public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, String constraintName, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException { if (constraintStructure == null) { return new ConstraintDefinitionNull(null, ownerOMA, blenderContext); } @@ -100,7 +100,9 @@ public class ConstraintDefinitionFactory { Class constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); if (constraintDefinitionClass != null) { try { - return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext); + ConstraintDefinition def = (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext); + def.setConstraintName(constraintName); + return def; } catch (IllegalArgumentException e) { throw new BlenderFileException(e.getLocalizedMessage(), e); } catch (SecurityException e) { @@ -113,9 +115,9 @@ public class ConstraintDefinitionFactory { throw new BlenderFileException(e.getLocalizedMessage(), e); } } else { - String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName); - if (constraintName != null) { - return new UnsupportedConstraintDefinition(constraintName); + String unsupportedConstraintClassName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName); + if (unsupportedConstraintClassName != null) { + return new UnsupportedConstraintDefinition(unsupportedConstraintClassName); } else { throw new BlenderFileException("Unknown constraint type: " + constraintClassName); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index 35a4e5618..820a283b3 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -1,36 +1,32 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; + +import org.ejml.simple.SimpleMatrix; import com.jme3.animation.Bone; import com.jme3.math.Transform; -import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.math.DQuaternion; import com.jme3.scene.plugins.blender.math.DTransform; +import com.jme3.scene.plugins.blender.math.Matrix; import com.jme3.scene.plugins.blender.math.Vector3d; -/** - * The Inverse Kinematics constraint. - * - * @author Wesley Shillingford (wezrule) - * @author Marcin Roguski (Kaelthas) - */ public class ConstraintDefinitionIK extends ConstraintDefinition { - private static final float MIN_DISTANCE = 0.0001f; + private static final float MIN_DISTANCE = 0.001f; private static final int FLAG_USE_TAIL = 0x01; private static final int FLAG_POSITION = 0x20; + private BonesChain bones; /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */ private int bonesAffected; - /** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */ - private double chainLength; /** Indicates if the tail of the bone should be used or not. */ private boolean useTail; /** The amount of iterations of the algorithm. */ @@ -51,122 +47,85 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { } } + @Override + public Set getAlteredOmas() { + return bones.alteredOmas; + } + @Override public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { if (influence == 0 || !trackToBeChanged || targetTransform == null) { return;// no need to do anything } + DQuaternion q = new DQuaternion(); Vector3d t = new Vector3d(targetTransform.getTranslation()); - List bones = this.loadBones(); + bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, blenderContext); if (bones.size() == 0) { return;// no need to do anything } double distanceFromTarget = Double.MAX_VALUE; - int iterations = this.iterations; - if (bones.size() == 1) { - iterations = 1;// if only one bone is in the chain then only one iteration that will properly rotate it will be needed - } else { - // if the target cannot be rached by the bones' chain then the chain will become straight and point towards the target - // in this case only one iteration will be needed, computed from the root to top bone - BoneContext rootBone = bones.get(bones.size() - 1); - Transform rootBoneTransform = constraintHelper.getTransform(rootBone.getArmatureObjectOMA(), rootBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD); - if (t.distance(new Vector3d(rootBoneTransform.getTranslation())) >= chainLength) { - Collections.reverse(bones); - - for (BoneContext boneContext : bones) { - Bone bone = boneContext.getBone(); - DTransform boneTransform = new DTransform(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD)); - - Vector3d e = boneTransform.getTranslation().add(boneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(boneContext.getLength()));// effector - Vector3d j = boneTransform.getTranslation(); // current join position - - Vector3d currentDir = e.subtractLocal(j).normalizeLocal(); - Vector3d target = t.subtract(j).normalizeLocal(); - double angle = currentDir.angleBetween(target); - if (angle != 0) { - Vector3d cross = currentDir.crossLocal(target).normalizeLocal(); - q.fromAngleAxis(angle, cross); - - if(bone.equals(this.getOwner())) { - if (boneContext.isLockX()) { - q.set(0, q.getY(), q.getZ(), q.getW()); - } - if (boneContext.isLockY()) { - q.set(q.getX(), 0, q.getZ(), q.getW()); - } - if (boneContext.isLockZ()) { - q.set(q.getX(), q.getY(), 0, q.getW()); - } - } - - boneTransform.getRotation().set(q.multLocal(boneTransform.getRotation())); - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneTransform.toTransform()); - } - } - - iterations = 0; + Vector3d target = new Vector3d(targetTransform.getTranslation()); + Vector3d[] rotationVectors = new Vector3d[bones.size()]; + BoneContext topBone = bones.get(0); + for (int i = 1; i <= iterations; ++i) { + DTransform topBoneTransform = bones.getWorldTransform(topBone); + Vector3d e = topBoneTransform.getTranslation().add(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector + distanceFromTarget = e.distance(t); + if (distanceFromTarget <= MIN_DISTANCE) { + break; } - } - List bestSolution = new ArrayList(bones.size()); - double bestSolutionDistance = Double.MAX_VALUE; - BoneContext topBone = bones.get(0); - for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) { - for (BoneContext boneContext : bones) { - Bone bone = boneContext.getBone(); - DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD)); - DTransform boneWorldTransform = new DTransform(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD)); + Matrix deltaP = new Matrix(3, 1); + deltaP.setColumn(target.subtract(e), 0); - Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector + Matrix J = new Matrix(3, bones.size()); + int column = 0; + for (BoneContext boneContext : bones) { + DTransform boneWorldTransform = bones.getWorldTransform(boneContext); Vector3d j = boneWorldTransform.getTranslation(); // current join position + Vector3d vectorFromJointToEffector = e.subtract(j); + rotationVectors[column] = vectorFromJointToEffector.cross(target.subtract(j)).normalize(); + Vector3d col = rotationVectors[column].cross(vectorFromJointToEffector); + J.setColumn(col, column++); + } + Matrix J_1 = J.pseudoinverse(); - Vector3d currentDir = e.subtractLocal(j).normalizeLocal(); - Vector3d target = t.subtract(j).normalizeLocal(); - double angle = currentDir.angleBetween(target); - if (angle != 0) { - Vector3d cross = currentDir.crossLocal(target).normalizeLocal(); - q.fromAngleAxis(angle, cross); - - if(bone.equals(this.getOwner())) { - if (boneContext.isLockX()) { - q.set(0, q.getY(), q.getZ(), q.getW()); - } - if (boneContext.isLockY()) { - q.set(q.getX(), 0, q.getZ(), q.getW()); - } - if (boneContext.isLockZ()) { - q.set(q.getX(), q.getY(), 0, q.getW()); - } - } + SimpleMatrix deltaThetas = J_1.mult(deltaP); - boneWorldTransform.getRotation().set(q.multLocal(boneWorldTransform.getRotation())); - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform.toTransform()); - } else { - iterations = 0; - break; - } - } + for (int j = 0; j < deltaThetas.numRows(); ++j) { + double angle = deltaThetas.get(j, 0); + Vector3d rotationVector = rotationVectors[j]; - DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD)); - Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector - distanceFromTarget = e.distance(t); - - if(distanceFromTarget < bestSolutionDistance) { - bestSolutionDistance = distanceFromTarget; - bestSolution.clear(); - for(BoneContext boneContext : bones) { - bestSolution.add(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD)); + q.fromAngleAxis(angle, rotationVector); + BoneContext boneContext = bones.get(j); + Bone bone = boneContext.getBone(); + if (bone.equals(this.getOwner())) { + if (boneContext.isLockX()) { + q.set(0, q.getY(), q.getZ(), q.getW()); + } + if (boneContext.isLockY()) { + q.set(q.getX(), 0, q.getZ(), q.getW()); + } + if (boneContext.isLockZ()) { + q.set(q.getX(), q.getY(), 0, q.getW()); + } } + + DTransform boneTransform = bones.getWorldTransform(boneContext); + boneTransform.getRotation().set(q.mult(boneTransform.getRotation())); + bones.setWorldTransform(boneContext, boneTransform); } } - - // applying best solution - for(int i=0;i= 0; --i) { BoneContext boneContext = bones.get(i); - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, bestSolution.get(i)); + DTransform transform = bones.getWorldTransform(boneContext); + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform.toTransform()); } + bones.reset(); } @Override @@ -174,49 +133,12 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { return "Inverse kinematics"; } - /** - * @return the bone contexts of all bones that will be used in this constraint computations - */ - private List loadBones() { - List bones = new ArrayList(); - Bone bone = (Bone) this.getOwner(); - if (bone == null) { - return bones; - } - if (!useTail) { - bone = bone.getParent(); - } - chainLength = 0; - while (bone != null) { - BoneContext boneContext = blenderContext.getBoneContext(bone); - chainLength += boneContext.getLength(); - bones.add(boneContext); - alteredOmas.add(boneContext.getBoneOma()); - if (bonesAffected != 0 && bones.size() >= bonesAffected) { - break; - } - // need to add spaces between bones to the chain length - Transform boneWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD); - Vector3f boneWorldTranslation = boneWorldTransform.getTranslation(); - - bone = bone.getParent(); - - if (bone != null) { - boneContext = blenderContext.getBoneContext(bone); - Transform parentWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD); - Vector3f parentWorldTranslation = parentWorldTransform.getTranslation(); - chainLength += boneWorldTranslation.distance(parentWorldTranslation); - } - } - return bones; - } - @Override public boolean isTrackToBeChanged() { if (trackToBeChanged) { // need to check the bone structure too (when constructor was called not all of the bones might have been loaded yet) // that is why it is also checked here - List bones = this.loadBones(); + bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, blenderContext); trackToBeChanged = bones.size() > 0; } return trackToBeChanged; @@ -226,4 +148,68 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { public boolean isTargetRequired() { return true; } + + private static class BonesChain extends ArrayList { + private static final long serialVersionUID = -1850524345643600718L; + + private Set alteredOmas = new HashSet(); + private List originalBonesMatrices = new ArrayList(); + private List bonesMatrices = new ArrayList(); + + public BonesChain(Bone bone, boolean useTail, int bonesAffected, BlenderContext blenderContext) { + if (bone != null) { + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + if (!useTail) { + bone = bone.getParent(); + } + while (bone != null && this.size() < bonesAffected) { + BoneContext boneContext = blenderContext.getBoneContext(bone); + this.add(boneContext); + alteredOmas.add(boneContext.getBoneOma()); + + Space space = this.size() < bonesAffected ? Space.CONSTRAINT_SPACE_LOCAL : Space.CONSTRAINT_SPACE_WORLD; + Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), space); + originalBonesMatrices.add(new DTransform(transform).toMatrix()); + + bone = bone.getParent(); + } + this.reset(); + } + } + + public DTransform getWorldTransform(BoneContext bone) { + int index = this.indexOf(bone); + return this.getWorldMatrix(index).toTransform(); + } + + public void setWorldTransform(BoneContext bone, DTransform transform) { + int index = this.indexOf(bone); + Matrix boneMatrix = transform.toMatrix(); + + if (index < this.size() - 1) { + // computing the current bone local transform + Matrix parentWorldMatrix = this.getWorldMatrix(index + 1); + SimpleMatrix m = parentWorldMatrix.invert().mult(boneMatrix); + boneMatrix = new Matrix(m); + } + bonesMatrices.set(index, boneMatrix); + } + + public Matrix getWorldMatrix(int index) { + if (index == this.size() - 1) { + return new Matrix(bonesMatrices.get(this.size() - 1)); + } + + SimpleMatrix result = this.getWorldMatrix(index + 1); + result = result.mult(bonesMatrices.get(index)); + return new Matrix(result); + } + + public void reset() { + bonesMatrices.clear(); + for (Matrix m : originalBonesMatrices) { + bonesMatrices.add(new Matrix(m)); + } + } + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java index 359abf057..9739ccd4b 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java @@ -164,6 +164,134 @@ public final class DQuaternion implements Savable, Cloneable, java.io.Serializab w = 1; } + /** + * norm returns the norm of this quaternion. This is the dot + * product of this quaternion with itself. + * + * @return the norm of the quaternion. + */ + public double norm() { + return w * w + x * x + y * y + z * z; + } + + public DQuaternion fromRotationMatrix(double m00, double m01, double m02, + double m10, double m11, double m12, double m20, double m21, double m22) { + // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix + // so that the scale does not affect the rotation + double lengthSquared = m00 * m00 + m10 * m10 + m20 * m20; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0 / Math.sqrt(lengthSquared); + m00 *= lengthSquared; + m10 *= lengthSquared; + m20 *= lengthSquared; + } + lengthSquared = m01 * m01 + m11 * m11 + m21 * m21; + if (lengthSquared != 1 && lengthSquared != 0f) { + lengthSquared = 1.0 / Math.sqrt(lengthSquared); + m01 *= lengthSquared; + m11 *= lengthSquared; + m21 *= lengthSquared; + } + lengthSquared = m02 * m02 + m12 * m12 + m22 * m22; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0 / Math.sqrt(lengthSquared); + m02 *= lengthSquared; + m12 *= lengthSquared; + m22 *= lengthSquared; + } + + // Use the Graphics Gems code, from + // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z + // *NOT* the "Matrix and Quaternions FAQ", which has errors! + + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + double t = m00 + m11 + m22; + + // we protect the division by s by ensuring that s>=1 + if (t >= 0) { // |w| >= .5 + double s = Math.sqrt(t + 1); // |s|>=1 ... + w = 0.5f * s; + s = 0.5f / s; // so this division isn't bad + x = (m21 - m12) * s; + y = (m02 - m20) * s; + z = (m10 - m01) * s; + } else if (m00 > m11 && m00 > m22) { + double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1 + x = s * 0.5f; // |x| >= .5 + s = 0.5f / s; + y = (m10 + m01) * s; + z = (m02 + m20) * s; + w = (m21 - m12) * s; + } else if (m11 > m22) { + double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1 + y = s * 0.5f; // |y| >= .5 + s = 0.5f / s; + x = (m10 + m01) * s; + z = (m21 + m12) * s; + w = (m02 - m20) * s; + } else { + double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1 + z = s * 0.5f; // |z| >= .5 + s = 0.5f / s; + x = (m02 + m20) * s; + y = (m21 + m12) * s; + w = (m10 - m01) * s; + } + + return this; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. 4th row and 4th column values are + * untouched. Note: the result is created from a normalized version of this quat. + * + * @param result + * The Matrix4f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix toRotationMatrix(Matrix result) { + Vector3d originalScale = new Vector3d(); + + result.toScaleVector(originalScale); + result.setScale(1, 1, 1); + double norm = this.norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + double s = norm == 1f ? 2f : norm > 0f ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + double xs = x * s; + double ys = y * s; + double zs = z * s; + double xx = x * xs; + double xy = x * ys; + double xz = x * zs; + double xw = w * xs; + double yy = y * ys; + double yz = y * zs; + double yw = w * ys; + double zz = z * zs; + double zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.set(0, 0, 1 - (yy + zz)); + result.set(0, 1, xy - zw); + result.set(0, 2, xz + yw); + result.set(1, 0, xy + zw); + result.set(1, 1, 1 - (xx + zz)); + result.set(1, 2, yz - xw); + result.set(2, 0, xz - yw); + result.set(2, 1, yz + xw); + result.set(2, 2, 1 - (xx + yy)); + + result.setScale(originalScale); + + return result; + } + /** * fromAngleAxis sets this quaternion to the values specified * by an angle and an axis of rotation. This method creates an object, so diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java index ed31a4c98..28fcda0c7 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java @@ -31,11 +31,15 @@ */ package com.jme3.scene.plugins.blender.math; -import com.jme3.export.*; -import com.jme3.math.Transform; - import java.io.IOException; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Transform; + /** * Started Date: Jul 16, 2004
    *
    @@ -57,6 +61,12 @@ public final class DTransform implements Savable, Cloneable, java.io.Serializabl private Vector3d translation; private Vector3d scale; + public DTransform() { + translation = new Vector3d(); + rotation = new DQuaternion(); + scale = new Vector3d(); + } + public DTransform(Transform transform) { translation = new Vector3d(transform.getTranslation()); rotation = new DQuaternion(transform.getRotation()); @@ -66,7 +76,15 @@ public final class DTransform implements Savable, Cloneable, java.io.Serializabl public Transform toTransform() { return new Transform(translation.toVector3f(), rotation.toQuaternion(), scale.toVector3f()); } - + + public Matrix toMatrix() { + Matrix m = Matrix.identity(4); + m.setTranslation(translation); + m.setRotationQuaternion(rotation); + m.setScale(scale); + return m; + } + /** * Sets this translation to the given value. * @param trans diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java new file mode 100644 index 000000000..5ea580b84 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java @@ -0,0 +1,214 @@ +package com.jme3.scene.plugins.blender.math; + +import java.text.DecimalFormat; + +import org.ejml.ops.CommonOps; +import org.ejml.simple.SimpleMatrix; +import org.ejml.simple.SimpleSVD; + +import com.jme3.math.FastMath; + +/** + * Encapsulates a 4x4 matrix + * + * + */ +public class Matrix extends SimpleMatrix { + private static final long serialVersionUID = 2396600537315902559L; + + public Matrix(int rows, int cols) { + super(rows, cols); + } + + /** + * Copy constructor + */ + public Matrix(SimpleMatrix m) { + super(m); + } + + public Matrix(double[][] data) { + super(data); + } + + public static Matrix identity(int size) { + Matrix result = new Matrix(size, size); + CommonOps.setIdentity(result.mat); + return result; + } + + public Matrix pseudoinverse() { + return this.pseudoinverse(1); + } + + @SuppressWarnings("unchecked") + public Matrix pseudoinverse(double lambda) { + SimpleSVD simpleSVD = this.svd(); + + SimpleMatrix U = simpleSVD.getU(); + SimpleMatrix S = simpleSVD.getW(); + SimpleMatrix V = simpleSVD.getV(); + + int N = Math.min(this.numRows(),this.numCols()); + double maxSingular = 0; + for( int i = 0; i < N; i++ ) { + if( S.get(i, i) > maxSingular ) { + maxSingular = S.get(i, i); + } + } + + double tolerance = FastMath.DBL_EPSILON * Math.max(this.numRows(),this.numCols()) * maxSingular; + for(int i=0;isetRotationQuaternion builds a rotation from a + * Quaternion. + * + * @param quat + * the quaternion to build the rotation from. + * @throws NullPointerException + * if quat is null. + */ + public void setRotationQuaternion(DQuaternion quat) { + quat.toRotationMatrix(this); + } + + public DTransform toTransform() { + DTransform result = new DTransform(); + result.setTranslation(this.toTranslationVector()); + result.setRotation(this.toRotationQuat()); + result.setScale(this.toScaleVector()); + return result; + } + + public Vector3d toTranslationVector() { + return new Vector3d(this.get(0, 3), this.get(1, 3), this.get(2, 3)); + } + + public DQuaternion toRotationQuat() { + DQuaternion quat = new DQuaternion(); + quat.fromRotationMatrix(this.get(0, 0), this.get(0, 1), this.get(0, 2), this.get(1, 0), this.get(1, 1), this.get(1, 2), this.get(2, 0), this.get(2, 1), this.get(2, 2)); + return quat; + } + + /** + * Retreives the scale vector from the matrix and stores it into a given + * vector. + * + * @param the + * vector where the scale will be stored + */ + public Vector3d toScaleVector() { + Vector3d result = new Vector3d(); + this.toScaleVector(result); + return result; + } + + /** + * Retreives the scale vector from the matrix and stores it into a given + * vector. + * + * @param the + * vector where the scale will be stored + */ + public void toScaleVector(Vector3d vector) { + double scaleX = Math.sqrt(this.get(0, 0) * this.get(0, 0) + this.get(1, 0) * this.get(1, 0) + this.get(2, 0) * this.get(2, 0)); + double scaleY = Math.sqrt(this.get(0, 1) * this.get(0, 1) + this.get(1, 1) * this.get(1, 1) + this.get(2, 1) * this.get(2, 1)); + double scaleZ = Math.sqrt(this.get(0, 2) * this.get(0, 2) + this.get(1, 2) * this.get(1, 2) + this.get(2, 2) * this.get(2, 2)); + vector.set(scaleX, scaleY, scaleZ); + } +} From b8fe36ed7650473097cc0f8fb58648d4cb725d1b Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 18 Jun 2015 23:00:17 -0400 Subject: [PATCH 198/225] glsllib with macros to convert glsl 1.1 shaders to 1.5 --- .../Common/ShaderLib/GLSL150Compat.glsllib | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 jme3-core/src/main/resources/Common/ShaderLib/GLSL150Compat.glsllib diff --git a/jme3-core/src/main/resources/Common/ShaderLib/GLSL150Compat.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/GLSL150Compat.glsllib new file mode 100644 index 000000000..336490696 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/GLSL150Compat.glsllib @@ -0,0 +1,14 @@ +#if _VERSION_ >= 150 +out vec4 outFragColor; +# define texture1D texture +# define texture2D texture +# define texture3D texture +# define texture2DLod texture +# if defined VERTEX_SHADER +# define varying out +# define attribute in +# elif defined FRAGMENT_SHADER +# define varying in +# define gl_FragColor outFragColor +# endif +#endif \ No newline at end of file From 43dc7345d0ea32f2cd3d00d520c6a4b0dfb10f9c Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Jun 2015 22:21:56 +0200 Subject: [PATCH 199/225] Changed to VertexUniformVectors --- jme3-core/src/main/java/com/jme3/renderer/Limits.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/Limits.java b/jme3-core/src/main/java/com/jme3/renderer/Limits.java index 7df9bc76b..86cddb178 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Limits.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Limits.java @@ -76,5 +76,5 @@ public enum Limits { DepthTextureSamples, - VertexUniformComponents, + VertexUniformVectors, } From 4cd0c5bffb17e8e7883c95eac03a5937c517464b Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Jun 2015 22:22:40 +0200 Subject: [PATCH 200/225] Added MAX_VERTEX_UNIFORM_VECTORS constant //untested however --- jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java | 1 + 1 file changed, 1 insertion(+) 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 21eedab52..04f496332 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 @@ -100,6 +100,7 @@ public interface GL { public static final int GL_MAX_VERTEX_ATTRIBS = 0x8869; public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; public static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A; + public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; public static final int GL_MIRRORED_REPEAT = 0x8370; public static final int GL_NEAREST = 0x2600; public static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702; From 8cb2be60feeeeea425759db9f3fc4d5208a91778 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Jun 2015 22:26:12 +0200 Subject: [PATCH 201/225] Added a switch to use VECTORS on GLES and COMPONENTS/4 on Desktop --- .../com/jme3/renderer/opengl/GLRenderer.java | 367 +++++++++--------- 1 file changed, 193 insertions(+), 174 deletions(-) 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 caf472543..2da5e28f9 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.MipMapGenerator; import com.jme3.util.NativeObjectManager; + import java.nio.*; import java.util.Arrays; import java.util.EnumMap; @@ -66,6 +67,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; + import jme3tools.shader.ShaderDebug; public class GLRenderer implements Renderer { @@ -73,7 +75,7 @@ public class GLRenderer implements Renderer { private static final Logger logger = Logger.getLogger(GLRenderer.class.getName()); private static final boolean VALIDATE_SHADER = false; private static final Pattern GLVERSION_PATTERN = Pattern.compile(".*?(\\d+)\\.(\\d+).*"); - + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); private final StringBuilder stringBuf = new StringBuilder(250); private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); @@ -83,7 +85,7 @@ public class GLRenderer implements Renderer { private final NativeObjectManager objManager = new NativeObjectManager(); private final EnumSet caps = EnumSet.noneOf(Caps.class); private final EnumMap limits = new EnumMap(Limits.class); - + private FrameBuffer mainFbOverride = null; private final Statistics statistics = new Statistics(); private int vpX, vpY, vpW, vpH; @@ -98,12 +100,12 @@ public class GLRenderer implements Renderer { private final GLExt glext; private final GLFbo glfbo; private final TextureUtil texUtil; - + 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.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 = glext; this.texUtil = new TextureUtil(gl, gl2, glext, context); @@ -118,7 +120,7 @@ public class GLRenderer implements Renderer { public EnumSet getCaps() { return caps; } - + // Not making public yet ... public EnumMap getLimits() { return limits; @@ -140,7 +142,7 @@ public class GLRenderer implements Renderer { } return extensionSet; } - + public static int extractVersion(String version) { Matcher m = GLVERSION_PATTERN.matcher(version); if (m.matches()) { @@ -160,17 +162,17 @@ public class GLRenderer implements Renderer { private boolean hasExtension(String extensionName) { return extensions.contains(extensionName); } - + private void loadCapabilitiesES() { caps.add(Caps.GLSL100); caps.add(Caps.OpenGLES20); - + // Important: Do not add OpenGL20 - that's the desktop capability! } - + private void loadCapabilitiesGL2() { int oglVer = extractVersion(gl.glGetString(GL.GL_VERSION)); - + if (oglVer >= 200) { caps.add(Caps.OpenGL20); if (oglVer >= 210) { @@ -194,7 +196,7 @@ public class GLRenderer implements Renderer { } } } - + int glslVer = extractVersion(gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)); switch (glslVer) { @@ -222,27 +224,27 @@ public class GLRenderer implements Renderer { caps.add(Caps.GLSL100); break; } - + // Workaround, always assume we support GLSL100 & GLSL110 // Supporting OpenGL 2.0 means supporting GLSL 1.10. caps.add(Caps.GLSL110); caps.add(Caps.GLSL100); - + // Fix issue in TestRenderToMemory when GL.GL_FRONT is the main // buffer being used. context.initialDrawBuf = getInteger(GL2.GL_DRAW_BUFFER); context.initialReadBuf = getInteger(GL2.GL_READ_BUFFER); - + // XXX: This has to be GL.GL_BACK for canvas on Mac // Since initialDrawBuf is GL.GL_FRONT for pbuffer, gotta // change this value later on ... // initialDrawBuf = GL.GL_BACK; // initialReadBuf = GL.GL_BACK; } - + private void loadCapabilitiesCommon() { extensions = loadExtensions(); - + limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS)); if (limits.get(Limits.VertexTextureUnits) > 0) { caps.add(Caps.VertexTextureFetch); @@ -257,62 +259,66 @@ public class GLRenderer implements Renderer { // gl.glGetInteger(GL.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); // fragUniforms = intBuf16.get(0); // logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - limits.put(Limits.VertexUniformComponents,getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS)); + if (caps.contains(Caps.OpenGLES20)) { + limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_VECTORS)); + } else { + limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS) / 4); + } limits.put(Limits.VertexAttributes, getInteger(GL.GL_MAX_VERTEX_ATTRIBS)); limits.put(Limits.TextureSize, getInteger(GL.GL_MAX_TEXTURE_SIZE)); limits.put(Limits.CubemapSize, getInteger(GL.GL_MAX_CUBE_MAP_TEXTURE_SIZE)); - if (hasExtension("GL_ARB_draw_instanced") && - hasExtension("GL_ARB_instanced_arrays")) { + if (hasExtension("GL_ARB_draw_instanced") && + hasExtension("GL_ARB_instanced_arrays")) { caps.add(Caps.MeshInstancing); } if (hasExtension("GL_OES_element_index_uint") || gl2 != null) { caps.add(Caps.IntegerIndexBuffer); } - + if (hasExtension("GL_ARB_texture_buffer_object")) { caps.add(Caps.TextureBuffer); } - + // == texture format extensions == - + boolean hasFloatTexture; hasFloatTexture = hasExtension("GL_OES_texture_half_float") && - hasExtension("GL_OES_texture_float"); - + hasExtension("GL_OES_texture_float"); + if (!hasFloatTexture) { hasFloatTexture = hasExtension("GL_ARB_texture_float") && - hasExtension("GL_ARB_half_float_pixel"); - + hasExtension("GL_ARB_half_float_pixel"); + if (!hasFloatTexture) { hasFloatTexture = caps.contains(Caps.OpenGL30); } } - + if (hasFloatTexture) { caps.add(Caps.FloatTexture); } - + if (hasExtension("GL_OES_depth_texture") || gl2 != null) { caps.add(Caps.DepthTexture); - + // TODO: GL_OES_depth24 } - - if (hasExtension("GL_OES_rgb8_rgba8") || - hasExtension("GL_ARM_rgba8") || - hasExtension("GL_EXT_texture_format_BGRA8888")) { + + if (hasExtension("GL_OES_rgb8_rgba8") || + hasExtension("GL_ARM_rgba8") || + hasExtension("GL_EXT_texture_format_BGRA8888")) { caps.add(Caps.Rgba8); } - + if (caps.contains(Caps.OpenGL30) || hasExtension("GL_OES_packed_depth_stencil")) { caps.add(Caps.PackedDepthStencilBuffer); } if (hasExtension("GL_ARB_color_buffer_float") && - hasExtension("GL_ARB_half_float_pixel")) { + hasExtension("GL_ARB_half_float_pixel")) { // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. caps.add(Caps.FloatColorBuffer); } @@ -321,44 +327,44 @@ public class GLRenderer implements Renderer { caps.add(Caps.FloatDepthBuffer); } - if ((hasExtension("GL_EXT_packed_float") && hasFloatTexture) || - caps.contains(Caps.OpenGL30)) { + if ((hasExtension("GL_EXT_packed_float") && hasFloatTexture) || + caps.contains(Caps.OpenGL30)) { // Either OpenGL3 is available or both packed_float & half_float_pixel. caps.add(Caps.PackedFloatColorBuffer); caps.add(Caps.PackedFloatTexture); } - + if (hasExtension("GL_EXT_texture_shared_exponent") || caps.contains(Caps.OpenGL30)) { caps.add(Caps.SharedExponentTexture); } - + if (hasExtension("GL_EXT_texture_compression_s3tc")) { caps.add(Caps.TextureCompressionS3TC); } - + if (hasExtension("GL_ARB_ES3_compatibility")) { caps.add(Caps.TextureCompressionETC2); caps.add(Caps.TextureCompressionETC1); } else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) { caps.add(Caps.TextureCompressionETC1); } - + // == end texture format extensions == - + if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) { caps.add(Caps.VertexBufferArray); } - if (hasExtension("GL_ARB_texture_non_power_of_two") || - hasExtension("GL_OES_texture_npot") || - caps.contains(Caps.OpenGL30)) { + if (hasExtension("GL_ARB_texture_non_power_of_two") || + hasExtension("GL_OES_texture_npot") || + caps.contains(Caps.OpenGL30)) { caps.add(Caps.NonPowerOfTwoTextures); } else { logger.log(Level.WARNING, "Your graphics card does not " - + "support non-power-of-2 textures. " - + "Some features might not work."); + + "support non-power-of-2 textures. " + + "Some features might not work."); } - + if (caps.contains(Caps.OpenGLES20)) { // OpenGL ES 2 has some limited support for NPOT textures caps.add(Caps.PartialNonPowerOfTwoTextures); @@ -374,14 +380,14 @@ public class GLRenderer implements Renderer { if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) { caps.add(Caps.FrameBuffer); - + 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); } - + if (hasExtension("GL_EXT_framebuffer_multisample")) { caps.add(Caps.FrameBufferMultisample); limits.put(Limits.FrameBufferSamples, getInteger(GLExt.GL_MAX_SAMPLES_EXT)); @@ -419,10 +425,10 @@ public class GLRenderer implements Renderer { } caps.add(Caps.Multisample); } - + // Supports sRGB pipeline. - if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) - || caps.contains(Caps.OpenGL30) ) { + if ((hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) + || caps.contains(Caps.OpenGL30)) { caps.add(Caps.Srgb); } @@ -430,47 +436,46 @@ public class GLRenderer implements Renderer { if (hasExtension("GL_ARB_seamless_cube_map") || caps.contains(Caps.OpenGL32)) { caps.add(Caps.SeamlessCubemap); } - + 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}\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), - caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility" - }); - + " * Vendor: {0}\n" + + " * Renderer: {1}\n" + + " * OpenGL Version: {2}\n" + + " * 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), + caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility" + }); + // Print capabilities (if fine logging is enabled) if (logger.isLoggable(Level.FINE)) { StringBuilder sb = new StringBuilder(); sb.append("Supported capabilities: \n"); - for (Caps cap : caps) - { + for (Caps cap : caps) { sb.append("\t").append(cap.toString()).append("\n"); } logger.log(Level.FINE, sb.toString()); } - + texUtil.initialize(caps); } - + private void loadCapabilities() { if (gl2 != null) { loadCapabilitiesGL2(); @@ -479,31 +484,31 @@ public class GLRenderer implements Renderer { } loadCapabilitiesCommon(); } - + private int getInteger(int en) { intBuf16.clear(); gl.glGetInteger(en, intBuf16); return intBuf16.get(0); } - + private boolean getBoolean(int en) { gl.glGetBoolean(en, nameBuf); - return nameBuf.get(0) != (byte)0; + return nameBuf.get(0) != (byte) 0; } - + @SuppressWarnings("fallthrough") public void initialize() { loadCapabilities(); - + // 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)) { @@ -535,9 +540,11 @@ public class GLRenderer implements Renderer { invalidateState(); } - /*********************************************************************\ - |* Render State *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Render State *| + * \******************************************************************** + */ public void setDepthRange(float start, float end) { gl.glDepthRange(start, end); } @@ -602,7 +609,7 @@ public class GLRenderer implements Renderer { } if (state.isDepthTest() && !context.depthTestEnabled) { - gl.glEnable(GL.GL_DEPTH_TEST); + gl.glEnable(GL.GL_DEPTH_TEST); gl.glDepthFunc(convertTestFunction(context.depthFunc)); context.depthTestEnabled = true; } else if (!state.isDepthTest() && context.depthTestEnabled) { @@ -714,7 +721,7 @@ public class GLRenderer implements Renderer { case Color: case Screen: gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); - break; + break; case Exclusion: gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR); break; @@ -814,9 +821,11 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Camera and World transforms *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Camera and World transforms *| + * \******************************************************************** + */ public void setViewPort(int x, int y, int w, int h) { if (x != vpX || vpY != y || vpW != w || vpH != h) { gl.glViewport(x, y, w, h); @@ -858,9 +867,11 @@ public class GLRenderer implements Renderer { gl.resetStats(); } - /*********************************************************************\ - |* Shaders *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Shaders *| + * \******************************************************************** + */ protected void updateUniformLocation(Shader shader, Uniform uniform) { int loc = gl.glGetUniformLocation(shader.getId(), uniform.getName()); if (loc < 0) { @@ -1040,12 +1051,12 @@ public class GLRenderer implements Renderer { boolean gles2 = caps.contains(Caps.OpenGLES20); String language = source.getLanguage(); - + if (gles2 && !language.equals("GLSL100")) { throw new RendererException("This shader cannot run in OpenGL ES 2. " - + "Only GLSL 1.00 shaders are supported."); + + "Only GLSL 1.00 shaders are supported."); } - + // Upload shader source. // Merge the defines and source code. stringBuf.setLength(0); @@ -1072,17 +1083,17 @@ public class GLRenderer implements Renderer { } } } - + if (linearizeSrgbImages) { stringBuf.append("#define SRGB 1\n"); } - + stringBuf.append(source.getDefines()); stringBuf.append(source.getSource()); - + intBuf1.clear(); intBuf1.put(0, stringBuf.length()); - gl.glShaderSource(id, new String[]{ stringBuf.toString() }, intBuf1); + gl.glShaderSource(id, new String[]{stringBuf.toString()}, intBuf1); gl.glCompileShader(id); gl.glGetShader(id, GL.GL_COMPILE_STATUS, intBuf1); @@ -1137,7 +1148,7 @@ public class GLRenderer implements Renderer { // If using GLSL 1.5, we bind the outputs for the user // For versions 3.3 and up, user should use layout qualifiers instead. boolean bindFragDataRequired = false; - + for (ShaderSource source : shader.getSources()) { if (source.isUpdateNeeded()) { updateShaderSourceData(source); @@ -1245,9 +1256,11 @@ public class GLRenderer implements Renderer { shader.resetObject(); } - /*********************************************************************\ - |* Framebuffers *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Framebuffers *| + * \******************************************************************** + */ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { copyFrameBuffer(src, dst, true); } @@ -1402,7 +1415,7 @@ public class GLRenderer implements Renderer { } else if (attachmentSlot < 0 || attachmentSlot >= 16) { throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); } - + return GLFbo.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; } @@ -1412,7 +1425,7 @@ public class GLRenderer implements Renderer { if (image.isUpdateNeeded()) { // Check NPOT requirements checkNonPowerOfTwo(tex); - + updateTexImageData(image, tex.getType(), 0, false); // NOTE: For depth textures, sets nearest/no-mips mode @@ -1476,7 +1489,7 @@ public class GLRenderer implements Renderer { } checkFrameBufferError(); - + fb.clearUpdateNeeded(); } @@ -1569,7 +1582,7 @@ public class GLRenderer implements Renderer { // update viewport to reflect framebuffer's resolution setViewPort(0, 0, fb.getWidth(), fb.getHeight()); - + if (context.boundFBO != fb.getId()) { glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId()); statistics.onFrameBufferUse(fb, true); @@ -1640,7 +1653,7 @@ public class GLRenderer implements Renderer { public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } - + private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) { if (fb != null) { RenderBuffer rb = fb.getColorBuffer(); @@ -1662,8 +1675,8 @@ public class GLRenderer implements Renderer { gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf); } - - public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { + + public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false); readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType); } @@ -1695,15 +1708,17 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Textures *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Textures *| + * \******************************************************************** + */ private int convertTextureType(Texture.Type type, int samples, int face) { if (samples > 1 && !caps.contains(Caps.TextureMultisample)) { - throw new RendererException("Multisample textures are not supported" + - " by the video hardware."); + throw new RendererException("Multisample textures are not supported" + + " by the video hardware."); } - + switch (type) { case TwoDimensional: if (samples > 1) { @@ -1723,8 +1738,8 @@ public class GLRenderer implements Renderer { } case ThreeDimensional: if (!caps.contains(Caps.OpenGL20)) { - throw new RendererException("3D textures are not supported" + - " by the video hardware."); + throw new RendererException("3D textures are not supported" + + " by the video hardware."); } return GL2.GL_TEXTURE_3D; case CubeMap: @@ -1752,7 +1767,7 @@ public class GLRenderer implements Renderer { } private int convertMinFilter(Texture.MinFilter filter, boolean haveMips) { - if (haveMips){ + if (haveMips) { switch (filter) { case Trilinear: return GL.GL_LINEAR_MIPMAP_LINEAR; @@ -1807,11 +1822,11 @@ public class GLRenderer implements Renderer { int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); boolean haveMips = true; - + if (image != null) { haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps(); } - + // filter things if (image.getLastTextureState().magFilter != tex.getMagFilter()) { int magFilter = convertMagFilter(tex.getMagFilter()); @@ -1834,7 +1849,7 @@ public class GLRenderer implements Renderer { context.seamlessCubemap = false; } } - + if (tex.getAnisotropicFilter() > 1) { if (caps.contains(Caps.TextureFilterAnisotropic)) { gl.glTexParameterf(target, @@ -1867,19 +1882,19 @@ public class GLRenderer implements Renderer { throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); } - if(tex.isNeedCompareModeUpdate() && gl2 != null){ + if (tex.isNeedCompareModeUpdate() && gl2 != null) { // R to Texture compare mode if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_R_TO_TEXTURE); - gl2.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY); + gl2.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY); if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_GEQUAL); } else { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL); } - }else{ - //restoring default value - gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE); + } else { + //restoring default value + gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE); } tex.compareModeUpdated(); } @@ -1888,10 +1903,10 @@ public class GLRenderer implements Renderer { /** * Validates if a potentially NPOT texture is supported by the hardware. *

    - * Textures with power-of-2 dimensions are supported on all hardware, however + * Textures with power-of-2 dimensions are supported on all hardware, however * non-power-of-2 textures may or may not be supported depending on which * texturing features are used. - * + * * @param tex The texture to validate. * @throws RendererException If the texture is not supported by the hardware */ @@ -1900,23 +1915,23 @@ public class GLRenderer implements Renderer { // Texture is power-of-2, safe to use. return; } - + if (caps.contains(Caps.NonPowerOfTwoTextures)) { // Texture is NPOT but it is supported by video hardware. return; } - + // Maybe we have some / partial support for NPOT? if (!caps.contains(Caps.PartialNonPowerOfTwoTextures)) { // Cannot use any type of NPOT texture (uncommon) throw new RendererException("non-power-of-2 textures are not " - + "supported by the video hardware"); + + "supported by the video hardware"); } - + // Partial NPOT supported.. if (tex.getMinFilter().usesMipMapLevels()) { throw new RendererException("non-power-of-2 textures with mip-maps " - + "are not supported by the video hardware"); + + "are not supported by the video hardware"); } switch (tex.getType()) { @@ -1924,7 +1939,7 @@ public class GLRenderer implements Renderer { case ThreeDimensional: if (tex.getWrap(WrapAxis.R) != Texture.WrapMode.EdgeClamp) { throw new RendererException("repeating non-power-of-2 textures " - + "are not supported by the video hardware"); + + "are not supported by the video hardware"); } // fallthrough intentional!!! case TwoDimensionalArray: @@ -1932,22 +1947,22 @@ public class GLRenderer implements Renderer { if (tex.getWrap(WrapAxis.S) != Texture.WrapMode.EdgeClamp || tex.getWrap(WrapAxis.T) != Texture.WrapMode.EdgeClamp) { throw new RendererException("repeating non-power-of-2 textures " - + "are not supported by the video hardware"); + + "are not supported by the video hardware"); } break; default: throw new UnsupportedOperationException("unrecongized texture type"); } } - + /** * Uploads the given image to the GL driver. - * - * @param img The image to upload - * @param type How the data in the image argument should be interpreted. - * @param unit The texture slot to be used to upload the image, not important + * + * @param img The image to upload + * @param type How the data in the image argument should be interpreted. + * @param unit The texture slot to be used to upload the image, not important * @param scaleToPot If true, the image will be scaled to power-of-2 dimensions - * before being uploaded. + * before being uploaded. */ public void updateTexImageData(Image img, Texture.Type type, int unit, boolean scaleToPot) { int texId = img.getId(); @@ -1968,7 +1983,7 @@ public class GLRenderer implements Renderer { gl.glActiveTexture(GL.GL_TEXTURE0 + unit); context.boundTextureUnit = unit; } - + gl.glBindTexture(target, texId); context.boundTextures[unit] = img; @@ -2011,12 +2026,12 @@ public class GLRenderer implements Renderer { throw new RendererException("Multisample textures are not supported by the video hardware"); } } - + // Check if graphics card doesn't support depth textures if (img.getFormat().isDepthFormat() && !caps.contains(Caps.DepthTexture)) { throw new RendererException("Depth textures are not supported by the video hardware"); } - + if (target == GL.GL_TEXTURE_CUBE_MAP) { // Check max texture size before upload int cubeSize = limits.get(Limits.CubemapSize); @@ -2053,12 +2068,12 @@ public class GLRenderer implements Renderer { if (!caps.contains(Caps.TextureArray)) { throw new RendererException("Texture arrays not supported by graphics hardware"); } - + List data = imageForUpload.getData(); - + // -1 index specifies prepare data for 2D Array texUtil.uploadTexture(imageForUpload, target, -1, linearizeSrgbImages); - + for (int i = 0; i < data.size(); i++) { // upload each slice of 2D array in turn // this time with the appropriate index @@ -2087,21 +2102,21 @@ public class GLRenderer implements Renderer { if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { // Check NPOT requirements boolean scaleToPot = false; - + try { checkNonPowerOfTwo(tex); } catch (RendererException ex) { if (logger.isLoggable(Level.WARNING)) { int nextWidth = FastMath.nearestPowerOfTwo(tex.getImage().getWidth()); int nextHeight = FastMath.nearestPowerOfTwo(tex.getImage().getHeight()); - logger.log(Level.WARNING, - "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + - "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + - " to " + nextWidth + "x" + nextHeight); + logger.log(Level.WARNING, + "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + + "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + + " to " + nextWidth + "x" + nextHeight); } scaleToPot = true; } - + updateTexImageData(image, tex.getType(), unit, scaleToPot); } @@ -2146,9 +2161,11 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Vertex Buffers and Attributes *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Vertex Buffers and Attributes *| + * \******************************************************************** + */ private int convertUsage(Usage usage) { switch (usage) { case Static: @@ -2222,7 +2239,7 @@ public class GLRenderer implements Renderer { //statistics.onVertexBufferUse(vb, false); } } - + int usage = convertUsage(vb.getUsage()); vb.getData().rewind(); @@ -2283,7 +2300,7 @@ public class GLRenderer implements Renderer { if (context.boundShaderProgram <= 0) { throw new IllegalStateException("Cannot render mesh without shader bound"); } - + Attribute attrib = context.boundShader.getAttribute(vb.getBufferType()); int loc = attrib.getLocation(); if (loc == -1) { @@ -2413,7 +2430,7 @@ public class GLRenderer implements Renderer { // What is this? throw new RendererException("Unexpected format for index buffer: " + indexBuf.getFormat()); } - + if (indexBuf.isUpdateNeeded()) { updateBufferData(indexBuf); } @@ -2486,9 +2503,11 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Render Calls *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Render Calls *| + * \******************************************************************** + */ public int convertElementMode(Mesh.Mode mode) { switch (mode) { case Points: @@ -2530,7 +2549,7 @@ public class GLRenderer implements Renderer { if (interleavedData != null && interleavedData.isUpdateNeeded()) { updateBufferData(interleavedData); } - + if (instanceData != null) { setVertexAttrib(instanceData, null); } @@ -2580,11 +2599,11 @@ public class GLRenderer implements Renderer { } private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - + // Here while count is still passed in. Can be removed when/if // the method is collapsed again. -pspeed count = Math.max(mesh.getInstanceCount(), count); - + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); if (interleavedData != null && interleavedData.isUpdateNeeded()) { updateBufferData(interleavedData); @@ -2602,7 +2621,7 @@ public class GLRenderer implements Renderer { setVertexAttrib(vb, null); } } - + for (VertexBuffer vb : mesh.getBufferList().getArray()) { if (vb.getBufferType() == Type.InterleavedData || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers @@ -2637,7 +2656,7 @@ public class GLRenderer implements Renderer { gl.glLineWidth(mesh.getLineWidth()); context.lineWidth = mesh.getLineWidth(); } - + if (gl4 != null && mesh.getMode().equals(Mode.Patch)) { gl4.glPatchParameter(mesh.getPatchVertexCount()); } @@ -2653,12 +2672,12 @@ public class GLRenderer implements Renderer { // Gamma correction if (!caps.contains(Caps.Srgb) && enableSrgb) { // Not supported, sorry. - logger.warning("sRGB framebuffer is not supported " + - "by video hardware, but was requested."); - + logger.warning("sRGB framebuffer is not supported " + + "by video hardware, but was requested."); + return; } - + setFrameBuffer(null); if (enableSrgb) { From 1e0468bdbe4a1f48a24fc426531c87a3a36c4023 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Jun 2015 23:07:28 +0200 Subject: [PATCH 202/225] Removed empty lines --- .../com/jme3/renderer/opengl/GLRenderer.java | 365 +++++++++--------- 1 file changed, 191 insertions(+), 174 deletions(-) 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 caf472543..23d3eea17 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 @@ -73,7 +73,7 @@ public class GLRenderer implements Renderer { private static final Logger logger = Logger.getLogger(GLRenderer.class.getName()); private static final boolean VALIDATE_SHADER = false; private static final Pattern GLVERSION_PATTERN = Pattern.compile(".*?(\\d+)\\.(\\d+).*"); - + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); private final StringBuilder stringBuf = new StringBuilder(250); private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); @@ -83,7 +83,7 @@ public class GLRenderer implements Renderer { private final NativeObjectManager objManager = new NativeObjectManager(); private final EnumSet caps = EnumSet.noneOf(Caps.class); private final EnumMap limits = new EnumMap(Limits.class); - + private FrameBuffer mainFbOverride = null; private final Statistics statistics = new Statistics(); private int vpX, vpY, vpW, vpH; @@ -98,12 +98,12 @@ public class GLRenderer implements Renderer { private final GLExt glext; private final GLFbo glfbo; private final TextureUtil texUtil; - + 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.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 = glext; this.texUtil = new TextureUtil(gl, gl2, glext, context); @@ -118,7 +118,7 @@ public class GLRenderer implements Renderer { public EnumSet getCaps() { return caps; } - + // Not making public yet ... public EnumMap getLimits() { return limits; @@ -140,7 +140,7 @@ public class GLRenderer implements Renderer { } return extensionSet; } - + public static int extractVersion(String version) { Matcher m = GLVERSION_PATTERN.matcher(version); if (m.matches()) { @@ -160,17 +160,17 @@ public class GLRenderer implements Renderer { private boolean hasExtension(String extensionName) { return extensions.contains(extensionName); } - + private void loadCapabilitiesES() { caps.add(Caps.GLSL100); caps.add(Caps.OpenGLES20); - + // Important: Do not add OpenGL20 - that's the desktop capability! } - + private void loadCapabilitiesGL2() { int oglVer = extractVersion(gl.glGetString(GL.GL_VERSION)); - + if (oglVer >= 200) { caps.add(Caps.OpenGL20); if (oglVer >= 210) { @@ -194,7 +194,7 @@ public class GLRenderer implements Renderer { } } } - + int glslVer = extractVersion(gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)); switch (glslVer) { @@ -222,27 +222,27 @@ public class GLRenderer implements Renderer { caps.add(Caps.GLSL100); break; } - + // Workaround, always assume we support GLSL100 & GLSL110 // Supporting OpenGL 2.0 means supporting GLSL 1.10. caps.add(Caps.GLSL110); caps.add(Caps.GLSL100); - + // Fix issue in TestRenderToMemory when GL.GL_FRONT is the main // buffer being used. context.initialDrawBuf = getInteger(GL2.GL_DRAW_BUFFER); context.initialReadBuf = getInteger(GL2.GL_READ_BUFFER); - + // XXX: This has to be GL.GL_BACK for canvas on Mac // Since initialDrawBuf is GL.GL_FRONT for pbuffer, gotta // change this value later on ... // initialDrawBuf = GL.GL_BACK; // initialReadBuf = GL.GL_BACK; } - + private void loadCapabilitiesCommon() { extensions = loadExtensions(); - + limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS)); if (limits.get(Limits.VertexTextureUnits) > 0) { caps.add(Caps.VertexTextureFetch); @@ -257,62 +257,66 @@ public class GLRenderer implements Renderer { // gl.glGetInteger(GL.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); // fragUniforms = intBuf16.get(0); // logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); - limits.put(Limits.VertexUniformComponents,getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS)); + if (caps.contains(Caps.OpenGLES20)) { + limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_VECTORS)); + } else { + limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS) / 4); + } limits.put(Limits.VertexAttributes, getInteger(GL.GL_MAX_VERTEX_ATTRIBS)); limits.put(Limits.TextureSize, getInteger(GL.GL_MAX_TEXTURE_SIZE)); limits.put(Limits.CubemapSize, getInteger(GL.GL_MAX_CUBE_MAP_TEXTURE_SIZE)); - if (hasExtension("GL_ARB_draw_instanced") && - hasExtension("GL_ARB_instanced_arrays")) { + if (hasExtension("GL_ARB_draw_instanced") && + hasExtension("GL_ARB_instanced_arrays")) { caps.add(Caps.MeshInstancing); } if (hasExtension("GL_OES_element_index_uint") || gl2 != null) { caps.add(Caps.IntegerIndexBuffer); } - + if (hasExtension("GL_ARB_texture_buffer_object")) { caps.add(Caps.TextureBuffer); } - + // == texture format extensions == - + boolean hasFloatTexture; hasFloatTexture = hasExtension("GL_OES_texture_half_float") && - hasExtension("GL_OES_texture_float"); - + hasExtension("GL_OES_texture_float"); + if (!hasFloatTexture) { hasFloatTexture = hasExtension("GL_ARB_texture_float") && - hasExtension("GL_ARB_half_float_pixel"); - + hasExtension("GL_ARB_half_float_pixel"); + if (!hasFloatTexture) { hasFloatTexture = caps.contains(Caps.OpenGL30); } } - + if (hasFloatTexture) { caps.add(Caps.FloatTexture); } - + if (hasExtension("GL_OES_depth_texture") || gl2 != null) { caps.add(Caps.DepthTexture); - + // TODO: GL_OES_depth24 } - - if (hasExtension("GL_OES_rgb8_rgba8") || - hasExtension("GL_ARM_rgba8") || - hasExtension("GL_EXT_texture_format_BGRA8888")) { + + if (hasExtension("GL_OES_rgb8_rgba8") || + hasExtension("GL_ARM_rgba8") || + hasExtension("GL_EXT_texture_format_BGRA8888")) { caps.add(Caps.Rgba8); } - + if (caps.contains(Caps.OpenGL30) || hasExtension("GL_OES_packed_depth_stencil")) { caps.add(Caps.PackedDepthStencilBuffer); } if (hasExtension("GL_ARB_color_buffer_float") && - hasExtension("GL_ARB_half_float_pixel")) { + hasExtension("GL_ARB_half_float_pixel")) { // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. caps.add(Caps.FloatColorBuffer); } @@ -321,44 +325,44 @@ public class GLRenderer implements Renderer { caps.add(Caps.FloatDepthBuffer); } - if ((hasExtension("GL_EXT_packed_float") && hasFloatTexture) || - caps.contains(Caps.OpenGL30)) { + if ((hasExtension("GL_EXT_packed_float") && hasFloatTexture) || + caps.contains(Caps.OpenGL30)) { // Either OpenGL3 is available or both packed_float & half_float_pixel. caps.add(Caps.PackedFloatColorBuffer); caps.add(Caps.PackedFloatTexture); } - + if (hasExtension("GL_EXT_texture_shared_exponent") || caps.contains(Caps.OpenGL30)) { caps.add(Caps.SharedExponentTexture); } - + if (hasExtension("GL_EXT_texture_compression_s3tc")) { caps.add(Caps.TextureCompressionS3TC); } - + if (hasExtension("GL_ARB_ES3_compatibility")) { caps.add(Caps.TextureCompressionETC2); caps.add(Caps.TextureCompressionETC1); } else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) { caps.add(Caps.TextureCompressionETC1); } - + // == end texture format extensions == - + if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) { caps.add(Caps.VertexBufferArray); } - if (hasExtension("GL_ARB_texture_non_power_of_two") || - hasExtension("GL_OES_texture_npot") || - caps.contains(Caps.OpenGL30)) { + if (hasExtension("GL_ARB_texture_non_power_of_two") || + hasExtension("GL_OES_texture_npot") || + caps.contains(Caps.OpenGL30)) { caps.add(Caps.NonPowerOfTwoTextures); } else { logger.log(Level.WARNING, "Your graphics card does not " - + "support non-power-of-2 textures. " - + "Some features might not work."); + + "support non-power-of-2 textures. " + + "Some features might not work."); } - + if (caps.contains(Caps.OpenGLES20)) { // OpenGL ES 2 has some limited support for NPOT textures caps.add(Caps.PartialNonPowerOfTwoTextures); @@ -374,14 +378,14 @@ public class GLRenderer implements Renderer { if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) { caps.add(Caps.FrameBuffer); - + 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); } - + if (hasExtension("GL_EXT_framebuffer_multisample")) { caps.add(Caps.FrameBufferMultisample); limits.put(Limits.FrameBufferSamples, getInteger(GLExt.GL_MAX_SAMPLES_EXT)); @@ -419,10 +423,10 @@ public class GLRenderer implements Renderer { } caps.add(Caps.Multisample); } - + // Supports sRGB pipeline. - if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) - || caps.contains(Caps.OpenGL30) ) { + if ((hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) + || caps.contains(Caps.OpenGL30)) { caps.add(Caps.Srgb); } @@ -430,47 +434,46 @@ public class GLRenderer implements Renderer { if (hasExtension("GL_ARB_seamless_cube_map") || caps.contains(Caps.OpenGL32)) { caps.add(Caps.SeamlessCubemap); } - + 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}\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), - caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility" - }); - + " * Vendor: {0}\n" + + " * Renderer: {1}\n" + + " * OpenGL Version: {2}\n" + + " * 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), + caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility" + }); + // Print capabilities (if fine logging is enabled) if (logger.isLoggable(Level.FINE)) { StringBuilder sb = new StringBuilder(); sb.append("Supported capabilities: \n"); - for (Caps cap : caps) - { + for (Caps cap : caps) { sb.append("\t").append(cap.toString()).append("\n"); } logger.log(Level.FINE, sb.toString()); } - + texUtil.initialize(caps); } - + private void loadCapabilities() { if (gl2 != null) { loadCapabilitiesGL2(); @@ -479,31 +482,31 @@ public class GLRenderer implements Renderer { } loadCapabilitiesCommon(); } - + private int getInteger(int en) { intBuf16.clear(); gl.glGetInteger(en, intBuf16); return intBuf16.get(0); } - + private boolean getBoolean(int en) { gl.glGetBoolean(en, nameBuf); - return nameBuf.get(0) != (byte)0; + return nameBuf.get(0) != (byte) 0; } - + @SuppressWarnings("fallthrough") public void initialize() { loadCapabilities(); - + // 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)) { @@ -535,9 +538,11 @@ public class GLRenderer implements Renderer { invalidateState(); } - /*********************************************************************\ - |* Render State *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Render State *| + * \******************************************************************** + */ public void setDepthRange(float start, float end) { gl.glDepthRange(start, end); } @@ -602,7 +607,7 @@ public class GLRenderer implements Renderer { } if (state.isDepthTest() && !context.depthTestEnabled) { - gl.glEnable(GL.GL_DEPTH_TEST); + gl.glEnable(GL.GL_DEPTH_TEST); gl.glDepthFunc(convertTestFunction(context.depthFunc)); context.depthTestEnabled = true; } else if (!state.isDepthTest() && context.depthTestEnabled) { @@ -714,7 +719,7 @@ public class GLRenderer implements Renderer { case Color: case Screen: gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); - break; + break; case Exclusion: gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR); break; @@ -814,9 +819,11 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Camera and World transforms *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Camera and World transforms *| + * \******************************************************************** + */ public void setViewPort(int x, int y, int w, int h) { if (x != vpX || vpY != y || vpW != w || vpH != h) { gl.glViewport(x, y, w, h); @@ -858,9 +865,11 @@ public class GLRenderer implements Renderer { gl.resetStats(); } - /*********************************************************************\ - |* Shaders *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Shaders *| + * \******************************************************************** + */ protected void updateUniformLocation(Shader shader, Uniform uniform) { int loc = gl.glGetUniformLocation(shader.getId(), uniform.getName()); if (loc < 0) { @@ -1040,12 +1049,12 @@ public class GLRenderer implements Renderer { boolean gles2 = caps.contains(Caps.OpenGLES20); String language = source.getLanguage(); - + if (gles2 && !language.equals("GLSL100")) { throw new RendererException("This shader cannot run in OpenGL ES 2. " - + "Only GLSL 1.00 shaders are supported."); + + "Only GLSL 1.00 shaders are supported."); } - + // Upload shader source. // Merge the defines and source code. stringBuf.setLength(0); @@ -1072,17 +1081,17 @@ public class GLRenderer implements Renderer { } } } - + if (linearizeSrgbImages) { stringBuf.append("#define SRGB 1\n"); } - + stringBuf.append(source.getDefines()); stringBuf.append(source.getSource()); - + intBuf1.clear(); intBuf1.put(0, stringBuf.length()); - gl.glShaderSource(id, new String[]{ stringBuf.toString() }, intBuf1); + gl.glShaderSource(id, new String[]{stringBuf.toString()}, intBuf1); gl.glCompileShader(id); gl.glGetShader(id, GL.GL_COMPILE_STATUS, intBuf1); @@ -1137,7 +1146,7 @@ public class GLRenderer implements Renderer { // If using GLSL 1.5, we bind the outputs for the user // For versions 3.3 and up, user should use layout qualifiers instead. boolean bindFragDataRequired = false; - + for (ShaderSource source : shader.getSources()) { if (source.isUpdateNeeded()) { updateShaderSourceData(source); @@ -1245,9 +1254,11 @@ public class GLRenderer implements Renderer { shader.resetObject(); } - /*********************************************************************\ - |* Framebuffers *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Framebuffers *| + * \******************************************************************** + */ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { copyFrameBuffer(src, dst, true); } @@ -1402,7 +1413,7 @@ public class GLRenderer implements Renderer { } else if (attachmentSlot < 0 || attachmentSlot >= 16) { throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); } - + return GLFbo.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; } @@ -1412,7 +1423,7 @@ public class GLRenderer implements Renderer { if (image.isUpdateNeeded()) { // Check NPOT requirements checkNonPowerOfTwo(tex); - + updateTexImageData(image, tex.getType(), 0, false); // NOTE: For depth textures, sets nearest/no-mips mode @@ -1476,7 +1487,7 @@ public class GLRenderer implements Renderer { } checkFrameBufferError(); - + fb.clearUpdateNeeded(); } @@ -1569,7 +1580,7 @@ public class GLRenderer implements Renderer { // update viewport to reflect framebuffer's resolution setViewPort(0, 0, fb.getWidth(), fb.getHeight()); - + if (context.boundFBO != fb.getId()) { glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId()); statistics.onFrameBufferUse(fb, true); @@ -1640,7 +1651,7 @@ public class GLRenderer implements Renderer { public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } - + private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) { if (fb != null) { RenderBuffer rb = fb.getColorBuffer(); @@ -1662,8 +1673,8 @@ public class GLRenderer implements Renderer { gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf); } - - public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { + + public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false); readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType); } @@ -1695,15 +1706,17 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Textures *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Textures *| + * \******************************************************************** + */ private int convertTextureType(Texture.Type type, int samples, int face) { if (samples > 1 && !caps.contains(Caps.TextureMultisample)) { - throw new RendererException("Multisample textures are not supported" + - " by the video hardware."); + throw new RendererException("Multisample textures are not supported" + + " by the video hardware."); } - + switch (type) { case TwoDimensional: if (samples > 1) { @@ -1723,8 +1736,8 @@ public class GLRenderer implements Renderer { } case ThreeDimensional: if (!caps.contains(Caps.OpenGL20)) { - throw new RendererException("3D textures are not supported" + - " by the video hardware."); + throw new RendererException("3D textures are not supported" + + " by the video hardware."); } return GL2.GL_TEXTURE_3D; case CubeMap: @@ -1752,7 +1765,7 @@ public class GLRenderer implements Renderer { } private int convertMinFilter(Texture.MinFilter filter, boolean haveMips) { - if (haveMips){ + if (haveMips) { switch (filter) { case Trilinear: return GL.GL_LINEAR_MIPMAP_LINEAR; @@ -1807,11 +1820,11 @@ public class GLRenderer implements Renderer { int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); boolean haveMips = true; - + if (image != null) { haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps(); } - + // filter things if (image.getLastTextureState().magFilter != tex.getMagFilter()) { int magFilter = convertMagFilter(tex.getMagFilter()); @@ -1834,7 +1847,7 @@ public class GLRenderer implements Renderer { context.seamlessCubemap = false; } } - + if (tex.getAnisotropicFilter() > 1) { if (caps.contains(Caps.TextureFilterAnisotropic)) { gl.glTexParameterf(target, @@ -1867,19 +1880,19 @@ public class GLRenderer implements Renderer { throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); } - if(tex.isNeedCompareModeUpdate() && gl2 != null){ + if (tex.isNeedCompareModeUpdate() && gl2 != null) { // R to Texture compare mode if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_R_TO_TEXTURE); - gl2.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY); + gl2.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY); if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_GEQUAL); } else { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL); } - }else{ - //restoring default value - gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE); + } else { + //restoring default value + gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE); } tex.compareModeUpdated(); } @@ -1888,10 +1901,10 @@ public class GLRenderer implements Renderer { /** * Validates if a potentially NPOT texture is supported by the hardware. *

    - * Textures with power-of-2 dimensions are supported on all hardware, however + * Textures with power-of-2 dimensions are supported on all hardware, however * non-power-of-2 textures may or may not be supported depending on which * texturing features are used. - * + * * @param tex The texture to validate. * @throws RendererException If the texture is not supported by the hardware */ @@ -1900,23 +1913,23 @@ public class GLRenderer implements Renderer { // Texture is power-of-2, safe to use. return; } - + if (caps.contains(Caps.NonPowerOfTwoTextures)) { // Texture is NPOT but it is supported by video hardware. return; } - + // Maybe we have some / partial support for NPOT? if (!caps.contains(Caps.PartialNonPowerOfTwoTextures)) { // Cannot use any type of NPOT texture (uncommon) throw new RendererException("non-power-of-2 textures are not " - + "supported by the video hardware"); + + "supported by the video hardware"); } - + // Partial NPOT supported.. if (tex.getMinFilter().usesMipMapLevels()) { throw new RendererException("non-power-of-2 textures with mip-maps " - + "are not supported by the video hardware"); + + "are not supported by the video hardware"); } switch (tex.getType()) { @@ -1924,7 +1937,7 @@ public class GLRenderer implements Renderer { case ThreeDimensional: if (tex.getWrap(WrapAxis.R) != Texture.WrapMode.EdgeClamp) { throw new RendererException("repeating non-power-of-2 textures " - + "are not supported by the video hardware"); + + "are not supported by the video hardware"); } // fallthrough intentional!!! case TwoDimensionalArray: @@ -1932,22 +1945,22 @@ public class GLRenderer implements Renderer { if (tex.getWrap(WrapAxis.S) != Texture.WrapMode.EdgeClamp || tex.getWrap(WrapAxis.T) != Texture.WrapMode.EdgeClamp) { throw new RendererException("repeating non-power-of-2 textures " - + "are not supported by the video hardware"); + + "are not supported by the video hardware"); } break; default: throw new UnsupportedOperationException("unrecongized texture type"); } } - + /** * Uploads the given image to the GL driver. - * - * @param img The image to upload - * @param type How the data in the image argument should be interpreted. - * @param unit The texture slot to be used to upload the image, not important + * + * @param img The image to upload + * @param type How the data in the image argument should be interpreted. + * @param unit The texture slot to be used to upload the image, not important * @param scaleToPot If true, the image will be scaled to power-of-2 dimensions - * before being uploaded. + * before being uploaded. */ public void updateTexImageData(Image img, Texture.Type type, int unit, boolean scaleToPot) { int texId = img.getId(); @@ -1968,7 +1981,7 @@ public class GLRenderer implements Renderer { gl.glActiveTexture(GL.GL_TEXTURE0 + unit); context.boundTextureUnit = unit; } - + gl.glBindTexture(target, texId); context.boundTextures[unit] = img; @@ -2011,12 +2024,12 @@ public class GLRenderer implements Renderer { throw new RendererException("Multisample textures are not supported by the video hardware"); } } - + // Check if graphics card doesn't support depth textures if (img.getFormat().isDepthFormat() && !caps.contains(Caps.DepthTexture)) { throw new RendererException("Depth textures are not supported by the video hardware"); } - + if (target == GL.GL_TEXTURE_CUBE_MAP) { // Check max texture size before upload int cubeSize = limits.get(Limits.CubemapSize); @@ -2053,12 +2066,12 @@ public class GLRenderer implements Renderer { if (!caps.contains(Caps.TextureArray)) { throw new RendererException("Texture arrays not supported by graphics hardware"); } - + List data = imageForUpload.getData(); - + // -1 index specifies prepare data for 2D Array texUtil.uploadTexture(imageForUpload, target, -1, linearizeSrgbImages); - + for (int i = 0; i < data.size(); i++) { // upload each slice of 2D array in turn // this time with the appropriate index @@ -2087,21 +2100,21 @@ public class GLRenderer implements Renderer { if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { // Check NPOT requirements boolean scaleToPot = false; - + try { checkNonPowerOfTwo(tex); } catch (RendererException ex) { if (logger.isLoggable(Level.WARNING)) { int nextWidth = FastMath.nearestPowerOfTwo(tex.getImage().getWidth()); int nextHeight = FastMath.nearestPowerOfTwo(tex.getImage().getHeight()); - logger.log(Level.WARNING, - "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + - "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + - " to " + nextWidth + "x" + nextHeight); + logger.log(Level.WARNING, + "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + + "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + + " to " + nextWidth + "x" + nextHeight); } scaleToPot = true; } - + updateTexImageData(image, tex.getType(), unit, scaleToPot); } @@ -2146,9 +2159,11 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Vertex Buffers and Attributes *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Vertex Buffers and Attributes *| + * \******************************************************************** + */ private int convertUsage(Usage usage) { switch (usage) { case Static: @@ -2222,7 +2237,7 @@ public class GLRenderer implements Renderer { //statistics.onVertexBufferUse(vb, false); } } - + int usage = convertUsage(vb.getUsage()); vb.getData().rewind(); @@ -2283,7 +2298,7 @@ public class GLRenderer implements Renderer { if (context.boundShaderProgram <= 0) { throw new IllegalStateException("Cannot render mesh without shader bound"); } - + Attribute attrib = context.boundShader.getAttribute(vb.getBufferType()); int loc = attrib.getLocation(); if (loc == -1) { @@ -2413,7 +2428,7 @@ public class GLRenderer implements Renderer { // What is this? throw new RendererException("Unexpected format for index buffer: " + indexBuf.getFormat()); } - + if (indexBuf.isUpdateNeeded()) { updateBufferData(indexBuf); } @@ -2486,9 +2501,11 @@ public class GLRenderer implements Renderer { } } - /*********************************************************************\ - |* Render Calls *| - \*********************************************************************/ + /** + * ******************************************************************\ + * |* Render Calls *| + * \******************************************************************** + */ public int convertElementMode(Mesh.Mode mode) { switch (mode) { case Points: @@ -2530,7 +2547,7 @@ public class GLRenderer implements Renderer { if (interleavedData != null && interleavedData.isUpdateNeeded()) { updateBufferData(interleavedData); } - + if (instanceData != null) { setVertexAttrib(instanceData, null); } @@ -2580,11 +2597,11 @@ public class GLRenderer implements Renderer { } private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - + // Here while count is still passed in. Can be removed when/if // the method is collapsed again. -pspeed count = Math.max(mesh.getInstanceCount(), count); - + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); if (interleavedData != null && interleavedData.isUpdateNeeded()) { updateBufferData(interleavedData); @@ -2602,7 +2619,7 @@ public class GLRenderer implements Renderer { setVertexAttrib(vb, null); } } - + for (VertexBuffer vb : mesh.getBufferList().getArray()) { if (vb.getBufferType() == Type.InterleavedData || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers @@ -2637,7 +2654,7 @@ public class GLRenderer implements Renderer { gl.glLineWidth(mesh.getLineWidth()); context.lineWidth = mesh.getLineWidth(); } - + if (gl4 != null && mesh.getMode().equals(Mode.Patch)) { gl4.glPatchParameter(mesh.getPatchVertexCount()); } @@ -2653,12 +2670,12 @@ public class GLRenderer implements Renderer { // Gamma correction if (!caps.contains(Caps.Srgb) && enableSrgb) { // Not supported, sorry. - logger.warning("sRGB framebuffer is not supported " + - "by video hardware, but was requested."); - + logger.warning("sRGB framebuffer is not supported " + + "by video hardware, but was requested."); + return; } - + setFrameBuffer(null); if (enableSrgb) { From a8b3407b469dcfff6ddd873768f7370f6e17ca49 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Jun 2015 23:08:00 +0200 Subject: [PATCH 203/225] Removed empty lines --- .../src/main/java/com/jme3/renderer/opengl/GLRenderer.java | 2 -- 1 file changed, 2 deletions(-) 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 2da5e28f9..23d3eea17 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,7 +56,6 @@ import com.jme3.util.BufferUtils; import com.jme3.util.ListMap; import com.jme3.util.MipMapGenerator; import com.jme3.util.NativeObjectManager; - import java.nio.*; import java.util.Arrays; import java.util.EnumMap; @@ -67,7 +66,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; - import jme3tools.shader.ShaderDebug; public class GLRenderer implements Renderer { From 8d0c371796851e7535d154a4a0a4ec6bdc669ad3 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Jun 2015 23:16:24 +0200 Subject: [PATCH 204/225] Hopefully fixed --- .../com/jme3/renderer/opengl/GLRenderer.java | 89 ++++++++----------- 1 file changed, 38 insertions(+), 51 deletions(-) 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 23d3eea17..bb145edbe 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 @@ -101,9 +101,9 @@ public class GLRenderer implements Renderer { 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.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 = glext; this.texUtil = new TextureUtil(gl, gl2, glext, context); @@ -425,8 +425,8 @@ public class GLRenderer implements Renderer { } // Supports sRGB pipeline. - if ((hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) - || caps.contains(Caps.OpenGL30)) { + if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) + || caps.contains(Caps.OpenGL30) ) { caps.add(Caps.Srgb); } @@ -465,7 +465,8 @@ public class GLRenderer implements Renderer { if (logger.isLoggable(Level.FINE)) { StringBuilder sb = new StringBuilder(); sb.append("Supported capabilities: \n"); - for (Caps cap : caps) { + for (Caps cap : caps) + { sb.append("\t").append(cap.toString()).append("\n"); } logger.log(Level.FINE, sb.toString()); @@ -491,7 +492,7 @@ public class GLRenderer implements Renderer { private boolean getBoolean(int en) { gl.glGetBoolean(en, nameBuf); - return nameBuf.get(0) != (byte) 0; + return nameBuf.get(0) != (byte)0; } @SuppressWarnings("fallthrough") @@ -538,11 +539,9 @@ public class GLRenderer implements Renderer { invalidateState(); } - /** - * ******************************************************************\ - * |* Render State *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Render State *| + \*********************************************************************/ public void setDepthRange(float start, float end) { gl.glDepthRange(start, end); } @@ -819,11 +818,9 @@ public class GLRenderer implements Renderer { } } - /** - * ******************************************************************\ - * |* Camera and World transforms *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Camera and World transforms *| + \*********************************************************************/ public void setViewPort(int x, int y, int w, int h) { if (x != vpX || vpY != y || vpW != w || vpH != h) { gl.glViewport(x, y, w, h); @@ -865,11 +862,9 @@ public class GLRenderer implements Renderer { gl.resetStats(); } - /** - * ******************************************************************\ - * |* Shaders *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Shaders *| + \*********************************************************************/ protected void updateUniformLocation(Shader shader, Uniform uniform) { int loc = gl.glGetUniformLocation(shader.getId(), uniform.getName()); if (loc < 0) { @@ -1091,7 +1086,7 @@ public class GLRenderer implements Renderer { intBuf1.clear(); intBuf1.put(0, stringBuf.length()); - gl.glShaderSource(id, new String[]{stringBuf.toString()}, intBuf1); + gl.glShaderSource(id, new String[]{ stringBuf.toString() }, intBuf1); gl.glCompileShader(id); gl.glGetShader(id, GL.GL_COMPILE_STATUS, intBuf1); @@ -1254,11 +1249,9 @@ public class GLRenderer implements Renderer { shader.resetObject(); } - /** - * ******************************************************************\ - * |* Framebuffers *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Framebuffers *| + \*********************************************************************/ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { copyFrameBuffer(src, dst, true); } @@ -1706,11 +1699,9 @@ public class GLRenderer implements Renderer { } } - /** - * ******************************************************************\ - * |* Textures *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Textures *| + \*********************************************************************/ private int convertTextureType(Texture.Type type, int samples, int face) { if (samples > 1 && !caps.contains(Caps.TextureMultisample)) { throw new RendererException("Multisample textures are not supported" + @@ -1765,7 +1756,7 @@ public class GLRenderer implements Renderer { } private int convertMinFilter(Texture.MinFilter filter, boolean haveMips) { - if (haveMips) { + if (haveMips){ switch (filter) { case Trilinear: return GL.GL_LINEAR_MIPMAP_LINEAR; @@ -1880,7 +1871,7 @@ public class GLRenderer implements Renderer { throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); } - if (tex.isNeedCompareModeUpdate() && gl2 != null) { + if(tex.isNeedCompareModeUpdate() && gl2 != null){ // R to Texture compare mode if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_R_TO_TEXTURE); @@ -1890,7 +1881,7 @@ public class GLRenderer implements Renderer { } else { gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL); } - } else { + }else{ //restoring default value gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE); } @@ -1901,7 +1892,7 @@ public class GLRenderer implements Renderer { /** * Validates if a potentially NPOT texture is supported by the hardware. *

    - * Textures with power-of-2 dimensions are supported on all hardware, however + * Textures with power-of-2 dimensions are supported on all hardware, however * non-power-of-2 textures may or may not be supported depending on which * texturing features are used. * @@ -1956,11 +1947,11 @@ public class GLRenderer implements Renderer { /** * Uploads the given image to the GL driver. * - * @param img The image to upload - * @param type How the data in the image argument should be interpreted. - * @param unit The texture slot to be used to upload the image, not important + * @param img The image to upload + * @param type How the data in the image argument should be interpreted. + * @param unit The texture slot to be used to upload the image, not important * @param scaleToPot If true, the image will be scaled to power-of-2 dimensions - * before being uploaded. + * before being uploaded. */ public void updateTexImageData(Image img, Texture.Type type, int unit, boolean scaleToPot) { int texId = img.getId(); @@ -2159,11 +2150,9 @@ public class GLRenderer implements Renderer { } } - /** - * ******************************************************************\ - * |* Vertex Buffers and Attributes *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Vertex Buffers and Attributes *| + \*********************************************************************/ private int convertUsage(Usage usage) { switch (usage) { case Static: @@ -2501,11 +2490,9 @@ public class GLRenderer implements Renderer { } } - /** - * ******************************************************************\ - * |* Render Calls *| - * \******************************************************************** - */ + /*********************************************************************\ + |* Render Calls *| + \*********************************************************************/ public int convertElementMode(Mesh.Mode mode) { switch (mode) { case Points: From 86900c9d0965f749266efff20e103245ab89c0a3 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 20 Jun 2015 14:28:41 -0400 Subject: [PATCH 205/225] Travis-CI: Try to fix notifications --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8a29bd1f8..f7b3add47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,10 @@ branches: notifications: slack: - secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" on_success: change on_failure: always + rooms: + secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" before_install: # required libs for android build tools From 5a1faac6294e076157bcd210dca17c5a122219ab Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 20 Jun 2015 14:37:01 -0400 Subject: [PATCH 206/225] Travis-CI: create zip dist as part of build --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f7b3add47..2b324fdbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,12 @@ notifications: rooms: secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" -before_install: +install: + - ./gradlew assemble +script: + - ./gradlew check + - ./gradlew createZipDistribution +# before_install: # required libs for android build tools # sudo apt-get update # sudo apt-get install -qq p7zip-full From c39abf48de49bd661b1f5b5fc2d7c1e3dc069373 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sat, 20 Jun 2015 15:59:45 -0400 Subject: [PATCH 207/225] Travis-CI: enable releases --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2b324fdbb..71c3b576f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,20 @@ notifications: install: - ./gradlew assemble + script: - ./gradlew check - ./gradlew createZipDistribution + +deploy: + provider: releases + api_key: + secure: "KbFiMt0a8FxUKvCJUYwikLYaqqGMn1p6k4OsXnGqwptQZEUIayabNLHeaD2kTNT3e6AY1ETwQLff/lB2LttmIo4g5NWW63g1K3A/HwgnhJwETengiProZ/Udl+ugPeDL/+ar43HUhFq4knBnzFKnEcHAThTPVqH/RMDvZf1UUYI=" + file: build/distributions/jME3.1.0_snapshot-github_2015-06-20.zip + skip_cleanup: true + on: + tags: true + # before_install: # required libs for android build tools # sudo apt-get update From 97a2f58be7b0d8e411bdc02c4c97e83ea004bfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=A4rtner?= Date: Mon, 22 Jun 2015 13:59:11 +0200 Subject: [PATCH 208/225] [Nifty-GUI] Using java logger, instead of stdout Replaced call to System.out.println() with proper logging call. --- .../src/main/java/com/jme3/cinematic/events/GuiEvent.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java index 2f5d788b4..f832b4266 100644 --- a/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java +++ b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java @@ -38,6 +38,8 @@ import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import de.lessvoid.nifty.Nifty; import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -45,6 +47,8 @@ import java.io.IOException; */ public class GuiEvent extends AbstractCinematicEvent { + static final Logger log = Logger.getLogger(GuiEvent.class.getName()); + protected String screen; protected Nifty nifty; @@ -76,7 +80,7 @@ public class GuiEvent extends AbstractCinematicEvent { @Override public void onPlay() { - System.out.println("screen should be " + screen); + log.log(Level.FINEST, "screen should be {0}", screen); nifty.gotoScreen(screen); } From 1eedfa5a5f3f53399f0dc78a35af92b187e418f6 Mon Sep 17 00:00:00 2001 From: Alrik Date: Mon, 22 Jun 2015 17:04:50 +0200 Subject: [PATCH 209/225] - fix GZIP and ZIP serializer copying the whole temporary buffer instead of only the part where the data was written. --- .../jme3/network/serializing/serializers/GZIPSerializer.java | 3 ++- .../jme3/network/serializing/serializers/ZIPSerializer.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java index 01dbd1d41..287d81cd0 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java @@ -87,7 +87,8 @@ public class GZIPSerializer extends Serializer { ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream(); GZIPOutputStream gzipOutput = new GZIPOutputStream(byteArrayOutput); - gzipOutput.write(tempBuffer.array()); + tempBuffer.flip(); + gzipOutput.write(tempBuffer.array(), 0, tempBuffer.limit()); gzipOutput.flush(); gzipOutput.finish(); gzipOutput.close(); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java index 1241c1341..ad7d7162d 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java @@ -98,7 +98,8 @@ public class ZIPSerializer extends Serializer { ZipEntry zipEntry = new ZipEntry("zip"); zipOutput.putNextEntry(zipEntry); - zipOutput.write(tempBuffer.array()); + tempBuffer.flip(); + zipOutput.write(tempBuffer.array(), 0, tempBuffer.limit()); zipOutput.flush(); zipOutput.closeEntry(); zipOutput.close(); From 2cf7d9eb5af40254b89320962c7a302b7744060b Mon Sep 17 00:00:00 2001 From: AlrikG Date: Mon, 22 Jun 2015 17:11:50 +0200 Subject: [PATCH 210/225] Update GZIPSerializer.java --- .../jme3/network/serializing/serializers/GZIPSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java index 287d81cd0..a4dd10098 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java @@ -88,7 +88,7 @@ public class GZIPSerializer extends Serializer { GZIPOutputStream gzipOutput = new GZIPOutputStream(byteArrayOutput); tempBuffer.flip(); - gzipOutput.write(tempBuffer.array(), 0, tempBuffer.limit()); + gzipOutput.write(tempBuffer.array(), 0, tempBuffer.limit()); gzipOutput.flush(); gzipOutput.finish(); gzipOutput.close(); From 7b6a742f0f2bea30bb44085af7edd150d2b387ab Mon Sep 17 00:00:00 2001 From: AlrikG Date: Mon, 22 Jun 2015 17:12:12 +0200 Subject: [PATCH 211/225] Update GZIPSerializer.java --- .../jme3/network/serializing/serializers/GZIPSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java index a4dd10098..7579e84c5 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java @@ -88,7 +88,7 @@ public class GZIPSerializer extends Serializer { GZIPOutputStream gzipOutput = new GZIPOutputStream(byteArrayOutput); tempBuffer.flip(); - gzipOutput.write(tempBuffer.array(), 0, tempBuffer.limit()); + gzipOutput.write(tempBuffer.array(), 0, tempBuffer.limit()); gzipOutput.flush(); gzipOutput.finish(); gzipOutput.close(); From 4cec908a50fecd7afcb27db76c34cd4ee8f80d0e Mon Sep 17 00:00:00 2001 From: kaelthas Date: Mon, 22 Jun 2015 18:41:53 +0200 Subject: [PATCH 212/225] Inverse Kinematics: several minor memory and CPU optimisations. --- .../definitions/ConstraintDefinitionIK.java | 111 ++++++++++-------- .../scene/plugins/blender/math/Matrix.java | 2 +- 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index 820a283b3..4fb9ecd74 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -1,9 +1,9 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Set; import org.ejml.simple.SimpleMatrix; @@ -19,6 +19,11 @@ import com.jme3.scene.plugins.blender.math.DTransform; import com.jme3.scene.plugins.blender.math.Matrix; import com.jme3.scene.plugins.blender.math.Vector3d; +/** + * A definiotion of a Inverse Kinematics constraint. This implementation uses Jacobian pseudoinverse algorithm. + * + * @author Marcin Roguski (Kaelthas) + */ public class ConstraintDefinitionIK extends ConstraintDefinition { private static final float MIN_DISTANCE = 0.001f; private static final int FLAG_USE_TAIL = 0x01; @@ -31,6 +36,8 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { private boolean useTail; /** The amount of iterations of the algorithm. */ private int iterations; + /** The count of bones' chain. */ + private int bonesCount = -1; public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { super(constraintData, ownerOMA, blenderContext); @@ -47,47 +54,64 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { } } - @Override - public Set getAlteredOmas() { - return bones.alteredOmas; - } + /** + * Below are the variables that only need to be allocated once for IK constraint instance. + */ + /** Temporal quaternion. */ + private DQuaternion tempDQuaternion = new DQuaternion(); + /** Temporal matrix column. */ + private Vector3d col = new Vector3d(); + /** Effector's position change. */ + private Matrix deltaP = new Matrix(3, 1); + /** The current target position. */ + private Vector3d target = new Vector3d(); + /** Rotation vectors for each joint (allocated when we know the size of a bones' chain. */ + private Vector3d[] rotationVectors; + /** The Jacobian matrix. Allocated when the bones' chain size is known. */ + private Matrix J; @Override public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || !trackToBeChanged || targetTransform == null) { + if (influence == 0 || !trackToBeChanged || targetTransform == null || bonesCount == 0) { return;// no need to do anything } - DQuaternion q = new DQuaternion(); - Vector3d t = new Vector3d(targetTransform.getTranslation()); - bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, blenderContext); + if (bones == null) { + bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, alteredOmas, blenderContext); + } if (bones.size() == 0) { + bonesCount = 0; return;// no need to do anything } double distanceFromTarget = Double.MAX_VALUE; + target.set(targetTransform.getTranslation().x, targetTransform.getTranslation().y, targetTransform.getTranslation().z); + + if (bonesCount < 0) { + bonesCount = bones.size(); + rotationVectors = new Vector3d[bonesCount]; + for (int i = 0; i < bonesCount; ++i) { + rotationVectors[i] = new Vector3d(); + } + J = new Matrix(3, bonesCount); + } - Vector3d target = new Vector3d(targetTransform.getTranslation()); - Vector3d[] rotationVectors = new Vector3d[bones.size()]; BoneContext topBone = bones.get(0); - for (int i = 1; i <= iterations; ++i) { + for (int i = 0; i < iterations; ++i) { DTransform topBoneTransform = bones.getWorldTransform(topBone); Vector3d e = topBoneTransform.getTranslation().add(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector - distanceFromTarget = e.distance(t); + distanceFromTarget = e.distance(target); if (distanceFromTarget <= MIN_DISTANCE) { break; } - Matrix deltaP = new Matrix(3, 1); - deltaP.setColumn(target.subtract(e), 0); - - Matrix J = new Matrix(3, bones.size()); + deltaP.setColumn(0, 0, target.x - e.x, target.y - e.y, target.z - e.z); int column = 0; for (BoneContext boneContext : bones) { DTransform boneWorldTransform = bones.getWorldTransform(boneContext); Vector3d j = boneWorldTransform.getTranslation(); // current join position Vector3d vectorFromJointToEffector = e.subtract(j); - rotationVectors[column] = vectorFromJointToEffector.cross(target.subtract(j)).normalize(); - Vector3d col = rotationVectors[column].cross(vectorFromJointToEffector); + vectorFromJointToEffector.cross(target.subtract(j), rotationVectors[column]).normalizeLocal(); + rotationVectors[column].cross(vectorFromJointToEffector, col); J.setColumn(col, column++); } Matrix J_1 = J.pseudoinverse(); @@ -98,34 +122,34 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { double angle = deltaThetas.get(j, 0); Vector3d rotationVector = rotationVectors[j]; - q.fromAngleAxis(angle, rotationVector); + tempDQuaternion.fromAngleAxis(angle, rotationVector); BoneContext boneContext = bones.get(j); Bone bone = boneContext.getBone(); if (bone.equals(this.getOwner())) { if (boneContext.isLockX()) { - q.set(0, q.getY(), q.getZ(), q.getW()); + tempDQuaternion.set(0, tempDQuaternion.getY(), tempDQuaternion.getZ(), tempDQuaternion.getW()); } if (boneContext.isLockY()) { - q.set(q.getX(), 0, q.getZ(), q.getW()); + tempDQuaternion.set(tempDQuaternion.getX(), 0, tempDQuaternion.getZ(), tempDQuaternion.getW()); } if (boneContext.isLockZ()) { - q.set(q.getX(), q.getY(), 0, q.getW()); + tempDQuaternion.set(tempDQuaternion.getX(), tempDQuaternion.getY(), 0, tempDQuaternion.getW()); } } DTransform boneTransform = bones.getWorldTransform(boneContext); - boneTransform.getRotation().set(q.mult(boneTransform.getRotation())); + boneTransform.getRotation().set(tempDQuaternion.mult(boneTransform.getRotation())); bones.setWorldTransform(boneContext, boneTransform); } } // applying the results - for (int i = bones.size() - 1; i >= 0; --i) { + for (int i = bonesCount - 1; i >= 0; --i) { BoneContext boneContext = bones.get(i); DTransform transform = bones.getWorldTransform(boneContext); constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform.toTransform()); } - bones.reset(); + bones = null;// need to reload them again } @Override @@ -133,30 +157,23 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { return "Inverse kinematics"; } - @Override - public boolean isTrackToBeChanged() { - if (trackToBeChanged) { - // need to check the bone structure too (when constructor was called not all of the bones might have been loaded yet) - // that is why it is also checked here - bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, blenderContext); - trackToBeChanged = bones.size() > 0; - } - return trackToBeChanged; - } - @Override public boolean isTargetRequired() { return true; } + /** + * Loaded bones' chain. This class allows to operate on transform matrices that use double precision in computations. + * Only the final result is being transformed to single precision numbers. + * + * @author Marcin Roguski (Kaelthas) + */ private static class BonesChain extends ArrayList { - private static final long serialVersionUID = -1850524345643600718L; + private static final long serialVersionUID = -1850524345643600718L; - private Set alteredOmas = new HashSet(); - private List originalBonesMatrices = new ArrayList(); - private List bonesMatrices = new ArrayList(); + private List bonesMatrices = new ArrayList(); - public BonesChain(Bone bone, boolean useTail, int bonesAffected, BlenderContext blenderContext) { + public BonesChain(Bone bone, boolean useTail, int bonesAffected, Collection alteredOmas, BlenderContext blenderContext) { if (bone != null) { ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); if (!useTail) { @@ -169,11 +186,10 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { Space space = this.size() < bonesAffected ? Space.CONSTRAINT_SPACE_LOCAL : Space.CONSTRAINT_SPACE_WORLD; Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), space); - originalBonesMatrices.add(new DTransform(transform).toMatrix()); + bonesMatrices.add(new DTransform(transform).toMatrix()); bone = bone.getParent(); } - this.reset(); } } @@ -204,12 +220,5 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { result = result.mult(bonesMatrices.get(index)); return new Matrix(result); } - - public void reset() { - bonesMatrices.clear(); - for (Matrix m : originalBonesMatrices) { - bonesMatrices.add(new Matrix(m)); - } - } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java index 5ea580b84..29550f23b 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java @@ -51,7 +51,7 @@ public class Matrix extends SimpleMatrix { int N = Math.min(this.numRows(),this.numCols()); double maxSingular = 0; - for( int i = 0; i < N; i++ ) { + for( int i = 0; i < N; ++i ) { if( S.get(i, i) > maxSingular ) { maxSingular = S.get(i, i); } From 483156f2bad72c3199350cd5d05ddd78172531e4 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 28 Jun 2015 15:11:04 +0200 Subject: [PATCH 213/225] Improvement: Inverse Kinematics now breaks the iteration if the computed angle change drops below some minimal level. --- .../definitions/ConstraintDefinitionIK.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index 4fb9ecd74..af6f17669 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -25,19 +25,20 @@ import com.jme3.scene.plugins.blender.math.Vector3d; * @author Marcin Roguski (Kaelthas) */ public class ConstraintDefinitionIK extends ConstraintDefinition { - private static final float MIN_DISTANCE = 0.001f; - private static final int FLAG_USE_TAIL = 0x01; - private static final int FLAG_POSITION = 0x20; + private static final float MIN_DISTANCE = 0.001f; + private static final float MIN_ANGLE_CHANGE = 0.001f; + private static final int FLAG_USE_TAIL = 0x01; + private static final int FLAG_POSITION = 0x20; - private BonesChain bones; + private BonesChain bones; /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */ - private int bonesAffected; + private int bonesAffected; /** Indicates if the tail of the bone should be used or not. */ - private boolean useTail; + private boolean useTail; /** The amount of iterations of the algorithm. */ - private int iterations; + private int iterations; /** The count of bones' chain. */ - private int bonesCount = -1; + private int bonesCount = -1; public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { super(constraintData, ownerOMA, blenderContext); @@ -117,7 +118,9 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { Matrix J_1 = J.pseudoinverse(); SimpleMatrix deltaThetas = J_1.mult(deltaP); - + if (deltaThetas.elementMaxAbs() < MIN_ANGLE_CHANGE) { + break; + } for (int j = 0; j < deltaThetas.numRows(); ++j) { double angle = deltaThetas.get(j, 0); Vector3d rotationVector = rotationVectors[j]; @@ -171,7 +174,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { private static class BonesChain extends ArrayList { private static final long serialVersionUID = -1850524345643600718L; - private List bonesMatrices = new ArrayList(); + private List bonesMatrices = new ArrayList(); public BonesChain(Bone bone, boolean useTail, int bonesAffected, Collection alteredOmas, BlenderContext blenderContext) { if (bone != null) { From 2d9a1b8737cfc0c4e4b346ebf3f2b55b32848a60 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 28 Jun 2015 15:48:11 +0200 Subject: [PATCH 214/225] Bugfix: include all bones in the chain of the IK constraint if the specified chain length is zero. --- .../blender/constraints/definitions/ConstraintDefinitionIK.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index af6f17669..69e087a28 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -182,7 +182,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { if (!useTail) { bone = bone.getParent(); } - while (bone != null && this.size() < bonesAffected) { + while (bone != null && (bonesAffected <= 0 || this.size() < bonesAffected)) { BoneContext boneContext = blenderContext.getBoneContext(bone); this.add(boneContext); alteredOmas.add(boneContext.getBoneOma()); From b0e751c81acfeea1de98da636a260d387a8bb42a Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 28 Jun 2015 15:48:49 +0200 Subject: [PATCH 215/225] Bugfix: avoiding infinite loops while applying constraints. --- .../blender/constraints/SimulationNode.java | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java index 031676b94..711e0a338 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.Stack; import java.util.logging.Logger; import com.jme3.animation.AnimChannel; @@ -38,9 +39,9 @@ import com.jme3.util.TempVars; * @author Marcin Roguski (Kaelthas) */ public class SimulationNode { - private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); + private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); - private Long featureOMA; + private Long featureOMA; /** The blender context. */ private BlenderContext blenderContext; /** The name of the node (for debugging purposes). */ @@ -51,11 +52,11 @@ public class SimulationNode { private List animations; /** The nodes spatial (if null then the boneContext should be set). */ - private Spatial spatial; + private Spatial spatial; /** The skeleton of the bone (not null if the node simulated the bone). */ - private Skeleton skeleton; + private Skeleton skeleton; /** Animation controller for the node's feature. */ - private AnimControl animControl; + private AnimControl animControl; /** * The star transform of a spatial. Needed to properly reset the spatial to @@ -64,7 +65,7 @@ public class SimulationNode { private Transform spatialStartTransform; /** Star transformations for bones. Needed to properly reset the bones. */ private Map boneStartTransforms; - + /** * Builds the nodes tree for the given feature. The feature (bone or * spatial) is found by its OMA. The feature must be a root bone or a root @@ -208,8 +209,7 @@ public class SimulationNode { if (animations != null) { TempVars vars = TempVars.get(); AnimChannel animChannel = animControl.createChannel(); - - // List bonesWithConstraints = this.collectBonesWithConstraints(skeleton); + for (Animation animation : animations) { float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); int maxFrame = (int) animationTimeBoundaries[0]; @@ -233,7 +233,7 @@ public class SimulationNode { for (Bone rootBone : skeleton.getRoots()) { // ignore the 0-indexed bone if (skeleton.getBoneIndex(rootBone) > 0) { - this.applyConstraints(rootBone, alteredOmas, applied, frame); + this.applyConstraints(rootBone, alteredOmas, applied, frame, new Stack()); } } @@ -294,34 +294,39 @@ public class SimulationNode { * the set of OMAS of the altered bones (is populated if necessary) * @param frame * the current frame of the animation + * @param bonesStack + * the stack of bones used to avoid infinite loops while applying constraints */ - private void applyConstraints(Bone bone, Set alteredOmas, Set applied, int frame) { - BoneContext boneContext = blenderContext.getBoneContext(bone); - if(!applied.contains(boneContext.getBoneOma())) { - List constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext); - if (constraints != null && constraints.size() > 0) { - // TODO: BEWARE OF INFINITE LOOPS !!!!!!!!!!!!!!!!!!!!!!!!!! - for (Constraint constraint : constraints) { - if (constraint.getTargetOMA() != null && constraint.getTargetOMA() > 0L) { - // first apply constraints of the target bone - BoneContext targetBone = blenderContext.getBoneContext(constraint.getTargetOMA()); - this.applyConstraints(targetBone.getBone(), alteredOmas, applied, frame); - } - constraint.apply(frame); - if (constraint.getAlteredOmas() != null) { - alteredOmas.addAll(constraint.getAlteredOmas()); + private void applyConstraints(Bone bone, Set alteredOmas, Set applied, int frame, Stack bonesStack) { + if (!bonesStack.contains(bone)) { + bonesStack.push(bone); + BoneContext boneContext = blenderContext.getBoneContext(bone); + if (!applied.contains(boneContext.getBoneOma())) { + List constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext); + if (constraints != null && constraints.size() > 0) { + for (Constraint constraint : constraints) { + if (constraint.getTargetOMA() != null && constraint.getTargetOMA() > 0L) { + // first apply constraints of the target bone + BoneContext targetBone = blenderContext.getBoneContext(constraint.getTargetOMA()); + this.applyConstraints(targetBone.getBone(), alteredOmas, applied, frame, bonesStack); + } + constraint.apply(frame); + if (constraint.getAlteredOmas() != null) { + alteredOmas.addAll(constraint.getAlteredOmas()); + } + alteredOmas.add(boneContext.getBoneOma()); } - alteredOmas.add(boneContext.getBoneOma()); } + applied.add(boneContext.getBoneOma()); } - applied.add(boneContext.getBoneOma()); - } - - List children = bone.getChildren(); - if (children != null && children.size() > 0) { - for (Bone child : bone.getChildren()) { - this.applyConstraints(child, alteredOmas, applied, frame); + + List children = bone.getChildren(); + if (children != null && children.size() > 0) { + for (Bone child : bone.getChildren()) { + this.applyConstraints(child, alteredOmas, applied, frame, bonesStack); + } } + bonesStack.pop(); } } From fe72dd67dd33f402b8e755f07001bb172f9156d2 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Fri, 3 Jul 2015 20:49:20 +0200 Subject: [PATCH 216/225] add TechniqueDef.noRender --- .../main/java/com/jme3/material/Material.java | 153 +++++++++--------- .../java/com/jme3/material/TechniqueDef.java | 135 +++++++++------- .../com/jme3/material/plugins/J3MLoader.java | 94 +++++------ 3 files changed, 205 insertions(+), 177 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 5f0ab8f91..44bfb73f5 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -69,7 +69,7 @@ import java.util.logging.Logger; * Setting the parameters can modify the behavior of a * shader. *

    - * + * * @author Kirill Vainer */ public class Material implements CloneableSmartAsset, Cloneable, Savable { @@ -146,7 +146,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { public String getName() { return name; } - + /** * This method sets the name of the material. * The name is not the same as the asset name. @@ -222,11 +222,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } /** - * Compares two materials and returns true if they are equal. + * Compares two materials and returns true if they are equal. * This methods compare definition, parameters, additional render states. - * Since materials are mutable objects, implementing equals() properly is not possible, + * Since materials are mutable objects, implementing equals() properly is not possible, * hence the name contentEquals(). - * + * * @param otherObj the material to compare to this material * @return true if the materials are equal. */ @@ -234,15 +234,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (!(otherObj instanceof Material)) { return false; } - + Material other = (Material) otherObj; - + // Early exit if the material are the same object if (this == other) { return true; } - // Check material definition + // Check material definition if (this.getMaterialDef() != other.getMaterialDef()) { return false; } @@ -251,12 +251,12 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (this.paramValues.size() != other.paramValues.size()) { return false; } - + // Checking technique if (this.technique != null || other.technique != null) { // Techniques are considered equal if their names are the same - // E.g. if user chose custom technique for one material but - // uses default technique for other material, the materials + // E.g. if user chose custom technique for one material but + // uses default technique for other material, the materials // are not equal. String thisDefName = this.technique != null ? this.technique.getDef().getName() : "Default"; String otherDefName = other.technique != null ? other.technique.getDef().getName() : "Default"; @@ -290,7 +290,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { return false; } } - + return true; } @@ -305,7 +305,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { hash = 29 * hash + (this.additionalState != null ? this.additionalState.contentHashCode() : 0); return hash; } - + /** * Returns the currently active technique. *

    @@ -436,7 +436,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { public Collection getParams() { return paramValues.values(); } - + /** * Returns the ListMap of all parameters set on this material. * @@ -473,7 +473,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { */ public void setParam(String name, VarType type, Object value) { checkSetParam(type, name); - + if (type.isTextureType()) { setTextureParam(name, type, (Texture)value); } else { @@ -501,7 +501,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (matParam == null) { return; } - + paramValues.remove(name); if (matParam instanceof MatParamTexture) { int texUnit = ((MatParamTexture) matParam).getUnit(); @@ -728,7 +728,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { renderer.renderMesh(mesh, lodLevel, 1, null); } } - + /** * Uploads the lights in the light list as two uniform arrays.

    * *

    @@ -747,30 +747,30 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { return 0; } - Uniform lightData = shader.getUniform("g_LightData"); - lightData.setVector4Length(numLights * 3);//8 lights * max 3 + Uniform lightData = shader.getUniform("g_LightData"); + lightData.setVector4Length(numLights * 3);//8 lights * max 3 Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - - if (startIndex != 0) { + + if (startIndex != 0) { // apply additive blending for 2nd and future passes rm.getRenderer().applyRenderState(additiveLight); - ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); }else{ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList,true)); } - + int lightDataIndex = 0; TempVars vars = TempVars.get(); Vector4f tmpVec = vars.vect4f1; int curIndex; int endIndex = numLights + startIndex; for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { - - - Light l = lightList.get(curIndex); + + + Light l = lightList.get(curIndex); if(l.getType() == Light.Type.Ambient){ - endIndex++; + endIndex++; continue; } ColorRGBA color = l.getColor(); @@ -781,14 +781,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { l.getType().getId(), lightDataIndex); lightDataIndex++; - + switch (l.getType()) { case Directional: DirectionalLight dl = (DirectionalLight) l; - Vector3f dir = dl.getDirection(); + Vector3f dir = dl.getDirection(); //Data directly sent in view space to avoid a matrix mult for each pixel tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); // tmpVec.divideLocal(tmpVec.w); // tmpVec.normalizeLocal(); lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); @@ -802,7 +802,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { Vector3f pos = pl.getPosition(); float invRadius = pl.getInvRadius(); tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); //tmpVec.divideLocal(tmpVec.w); lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); lightDataIndex++; @@ -810,37 +810,37 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { lightData.setVector4InArray(0,0,0,0, lightDataIndex); lightDataIndex++; break; - case Spot: + case Spot: SpotLight sl = (SpotLight) l; Vector3f pos2 = sl.getPosition(); Vector3f dir2 = sl.getDirection(); float invRange = sl.getInvSpotRange(); float spotAngleCos = sl.getPackedAngleCos(); tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); // tmpVec.divideLocal(tmpVec.w); lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); lightDataIndex++; - + //We transform the spot direction in view space here to save 5 varying later in the lighting shader //one vec4 less and a vec4 that becomes a vec3 //the downside is that spotAngleCos decoding happens now in the frag shader. tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); tmpVec.normalizeLocal(); lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); lightDataIndex++; - break; + break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); } } - vars.release(); + vars.release(); //Padding of unsued buffer space while(lightDataIndex < numLights * 3) { lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); - lightDataIndex++; - } + lightDataIndex++; + } return curIndex; } @@ -887,10 +887,10 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { case Directional: DirectionalLight dl = (DirectionalLight) l; Vector3f dir = dl.getDirection(); - //FIXME : there is an inconstency here due to backward + //FIXME : there is an inconstency here due to backward //compatibility of the lighting shader. - //The directional light direction is passed in the - //LightPosition uniform. The lighting shader needs to be + //The directional light direction is passed in the + //LightPosition uniform. The lighting shader needs to be //reworked though in order to fix this. tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); lightPos.setValue(VarType.Vector4, tmpLightPosition); @@ -987,11 +987,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { for (TechniqueDef techDef : techDefs) { if (rendererCaps.containsAll(techDef.getRequiredCaps())) { // use the first one that supports all the caps - tech = new Technique(this, techDef); + tech = new Technique(this, techDef); techniques.put(name, tech); if(tech.getDef().getLightMode() == renderManager.getPreferredLightMode() || tech.getDef().getLightMode() == LightMode.Disable){ - break; + break; } } lastTech = techDef; @@ -1078,7 +1078,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { Uniform u = uniforms.getValue(i); if (!u.isSetByCurrentMaterial()) { if (u.getName().charAt(0) != 'g') { - // Don't reset world globals! + // Don't reset world globals! // The benefits gained from this are very minimal // and cause lots of matrix -> FloatBuffer conversions. u.clearValue(); @@ -1093,21 +1093,21 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { *

    * The material is rendered as follows: *

      - *
    • Determine which technique to use to render the material - - * either what the user selected via - * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) - * Material.selectTechnique()}, - * or the first default technique that the renderer supports + *
    • Determine which technique to use to render the material - + * either what the user selected via + * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + * Material.selectTechnique()}, + * or the first default technique that the renderer supports * (based on the technique's {@link TechniqueDef#getRequiredCaps() requested rendering capabilities})
        - *
      • If the technique has been changed since the last frame, then it is notified via - * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, java.util.EnumSet) - * Technique.makeCurrent()}. - * If the technique wants to use a shader to render the model, it should load it at this part - - * the shader should have all the proper defines as declared in the technique definition, - * including those that are bound to material parameters. - * The technique can re-use the shader from the last frame if + *
      • If the technique has been changed since the last frame, then it is notified via + * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, java.util.EnumSet) + * Technique.makeCurrent()}. + * If the technique wants to use a shader to render the model, it should load it at this part - + * the shader should have all the proper defines as declared in the technique definition, + * including those that are bound to material parameters. + * The technique can re-use the shader from the last frame if * no changes to the defines occurred.
      - *
    • Set the {@link RenderState} to use for rendering. The render states are + *
    • Set the {@link RenderState} to use for rendering. The render states are * applied in this order (later RenderStates override earlier RenderStates):
        *
      1. {@link TechniqueDef#getRenderState() Technique Definition's RenderState} * - i.e. specific renderstate that is required for the shader.
      2. @@ -1120,22 +1120,22 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { *
      3. Uniforms bound to material parameters are updated based on the current material parameter values.
      4. *
      5. Uniforms bound to world parameters are updated from the RenderManager. * Internally {@link UniformBindingManager} is used for this task.
      6. - *
      7. Uniforms bound to textures will cause the texture to be uploaded as necessary. + *
      8. Uniforms bound to textures will cause the texture to be uploaded as necessary. * The uniform is set to the texture unit where the texture is bound.
    - *

    L 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

  • If the technique uses a shader, the model is then rendered according + *
  • If the technique uses a shader, the model is then rendered according * to the lighting mode specified on the technique definition.
      - *
    • {@link LightMode#SinglePass single pass light mode} fills the shader's light uniform arrays + *
    • {@link LightMode#SinglePass single pass light mode} fills the shader's light uniform arrays * with the first 4 lights and renders the model once.
    • - *
    • {@link LightMode#MultiPass multi pass light mode} light mode renders the model multiple times, - * for the first light it is rendered opaque, on subsequent lights it is + *
    • {@link LightMode#MultiPass multi pass light mode} light mode renders the model multiple times, + * for the first light it is rendered opaque, on subsequent lights it is * rendered with {@link BlendMode#AlphaAdditive alpha-additive} blending and depth writing disabled.
    • *
    - *
  • For techniques that do not use shaders, + *
  • For techniques that do not use shaders, * fixed function OpenGL is used to render the model (see {@link GL1Renderer} interface):
      *
    • OpenGL state ({@link FixedFuncBinding}) that is bound to material parameters is updated.
    • - *
    • The texture set on the material is uploaded and bound. + *
    • The texture set on the material is uploaded and bound. * Currently only 1 texture is supported for fixed function techniques.
    • - *
    • If the technique uses lighting, then OpenGL lighting state is updated + *
    • If the technique uses lighting, then OpenGL lighting state is updated * based on the light list on the geometry, otherwise OpenGL lighting is disabled.
    • *
    • The mesh is uploaded and rendered.
    • *
    @@ -1147,10 +1147,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { */ public void render(Geometry geom, LightList lights, RenderManager rm) { autoSelectTechnique(rm); + TechniqueDef techDef = technique.getDef(); - Renderer r = rm.getRenderer(); + if (techDef.isNoRender()) return; - TechniqueDef techDef = technique.getDef(); + Renderer r = rm.getRenderer(); if (rm.getForcedRenderState() != null) { r.applyRenderState(rm.getForcedRenderState()); @@ -1169,7 +1170,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { // reset unchanged uniform flag clearUniformsSetByCurrent(technique.getShader()); rm.updateUniformBindings(technique.getWorldBindUniforms()); - + // setup textures and uniforms for (int i = 0; i < paramValues.size(); i++) { @@ -1212,24 +1213,24 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { // any unset uniforms will be set to 0 resetUniformsNotSetByCurrent(shader); r.setShader(shader); - + renderMeshFromGeometry(r, geom); } /** * Called by {@link RenderManager} to render the geometry by * using this material. - * + * * Note that this version of the render method * does not perform light filtering. - * + * * @param geom The geometry to render * @param rm The render manager requesting the rendering */ public void render(Geometry geom, RenderManager rm) { render(geom, geom.getWorldLightList(), rm); } - + public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(def.getAssetName(), "material_def", null); @@ -1304,14 +1305,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { continue; } } - + if (im.getFormatVersion() == 0 && param.getName().startsWith("m_")) { // Ancient version of jME3 ... param.setName(param.getName().substring(2)); } - + if (def.getMaterialParam(param.getName()) == null) { - logger.log(Level.WARNING, "The material parameter is not defined: {0}. Ignoring..", + logger.log(Level.WARNING, "The material parameter is not defined: {0}. Ignoring..", param.getName()); } else { checkSetParam(param.getVarType(), param.getName()); diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java index f8152e563..d7523956c 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -40,7 +40,7 @@ import java.util.*; /** * Describes a technique definition. - * + * * @author Kirill Vainer */ public class TechniqueDef implements Savable { @@ -49,7 +49,7 @@ public class TechniqueDef implements Savable { * Version #1: Separate shader language for each shader source. */ public static final int SAVABLE_VERSION = 1; - + /** * Describes light rendering mode. */ @@ -58,15 +58,15 @@ public class TechniqueDef implements Savable { * Disable light-based rendering */ Disable, - + /** - * Enable light rendering by using a single pass. + * Enable light rendering by using a single pass. *

    * An array of light positions and light colors is passed to the shader * containing the world light list for the geometry being rendered. */ SinglePass, - + /** * Enable light rendering by using multi-pass rendering. *

    @@ -77,7 +77,7 @@ public class TechniqueDef implements Savable { * passes have it set to black. */ MultiPass, - + /** * @deprecated OpenGL1 is not supported anymore */ @@ -96,15 +96,16 @@ public class TechniqueDef implements Savable { private EnumMap shaderLanguages; private EnumMap shaderNames; - + private DefineList presetDefines; private boolean usesNodes = false; private List shaderNodes; private ShaderGenerationInfo shaderGenerationInfo; + private boolean noRender = false; private RenderState renderState; private RenderState forcedRenderState; - + private LightMode lightMode = LightMode.Disable; private ShadowMode shadowMode = ShadowMode.Disable; @@ -115,7 +116,7 @@ public class TechniqueDef implements Savable { * Creates a new technique definition. *

    * Used internally by the J3M/J3MD loader. - * + * * @param name The name of the technique, should be set to null * for default techniques. */ @@ -135,7 +136,7 @@ public class TechniqueDef implements Savable { /** * Returns the name of this technique as specified in the J3MD file. * Default techniques have the name "Default". - * + * * @return the name of this technique */ public String getName(){ @@ -153,9 +154,9 @@ public class TechniqueDef implements Savable { /** * Set the light mode - * + * * @param lightMode the light mode - * + * * @see LightMode */ public void setLightMode(LightMode lightMode) { @@ -172,9 +173,9 @@ public class TechniqueDef implements Savable { /** * Set the shadow mode. - * + * * @param shadowMode the shadow mode. - * + * * @see ShadowMode */ public void setShadowMode(ShadowMode shadowMode) { @@ -184,7 +185,7 @@ public class TechniqueDef implements Savable { /** * Returns the render state that this technique is using * @return the render state that this technique is using - * @see #setRenderState(com.jme3.material.RenderState) + * @see #setRenderState(com.jme3.material.RenderState) */ public RenderState getRenderState() { return renderState; @@ -192,15 +193,37 @@ public class TechniqueDef implements Savable { /** * Sets the render state that this technique is using. - * + * * @param renderState the render state that this technique is using. - * + * * @see RenderState */ public void setRenderState(RenderState renderState) { this.renderState = renderState; } + /** + * Sets if this technique should not be used to render. + * + * @param noRender not render or render ? + * + * @see NoRender + */ + public void setNoRender(boolean noRender) { + this.noRender = noRender; + } + + /** + * Returns true if this technique should not be used to render. + * (eg. to not render a material with default technique) + * + * @return true if this technique should not be rendered, false otherwise. + * + */ + public boolean isNoRender(){ + return noRender; + } + /** * @deprecated jME3 always requires shaders now */ @@ -208,12 +231,12 @@ public class TechniqueDef implements Savable { public boolean isUsingShaders(){ return true; } - + /** * Returns true if this technique uses Shader Nodes, false otherwise. - * + * * @return true if this technique uses Shader Nodes, false otherwise. - * + * */ public boolean isUsingShaderNodes(){ return usesNodes; @@ -222,7 +245,7 @@ public class TechniqueDef implements Savable { /** * Gets the {@link Caps renderer capabilities} that are required * by this technique. - * + * * @return the required renderer capabilities */ public EnumSet getRequiredCaps() { @@ -231,7 +254,7 @@ public class TechniqueDef implements Savable { /** * Sets the shaders that this technique definition will use. - * + * * @param vertexShader The name of the vertex shader * @param fragmentShader The name of the fragment shader * @param vertLanguage The vertex shader language @@ -242,7 +265,7 @@ public class TechniqueDef implements Savable { this.shaderNames.put(Shader.ShaderType.Vertex, vertexShader); this.shaderLanguages.put(Shader.ShaderType.Fragment, fragLanguage); this.shaderNames.put(Shader.ShaderType.Fragment, fragmentShader); - + requiredCaps.clear(); Caps vertCap = Caps.valueOf(vertLanguage); requiredCaps.add(vertCap); @@ -259,17 +282,17 @@ public class TechniqueDef implements Savable { */ public void setShaderFile(EnumMap shaderNames, EnumMap shaderLanguages) { requiredCaps.clear(); - + for (Shader.ShaderType shaderType : shaderNames.keySet()) { String language = shaderLanguages.get(shaderType); String shaderFile = shaderNames.get(shaderType); - + this.shaderLanguages.put(shaderType, language); this.shaderNames.put(shaderType, shaderFile); - + Caps vertCap = Caps.valueOf(language); requiredCaps.add(vertCap); - + if (shaderType.equals(Shader.ShaderType.Geometry)) { requiredCaps.add(Caps.GeometryShader); } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) { @@ -280,11 +303,11 @@ public class TechniqueDef implements Savable { /** * Returns the define name which the given material parameter influences. - * + * * @param paramName The parameter name to look up * @return The define name - * - * @see #addShaderParamDefine(java.lang.String, java.lang.String) + * + * @see #addShaderParamDefine(java.lang.String, java.lang.String) */ public String getShaderParamDefine(String paramName){ if (defineParams == null) { @@ -297,11 +320,11 @@ public class TechniqueDef implements Savable { * Adds a define linked to a material parameter. *

    * Any time the material parameter on the parent material is altered, - * the appropriate define on the technique will be modified as well. - * See the method + * the appropriate define on the technique will be modified as well. + * See the method * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } * on the exact details of how the material parameter changes the define. - * + * * @param paramName The name of the material parameter to link to. * @param defineName The name of the define parameter, e.g. USE_LIGHTING */ @@ -314,26 +337,26 @@ public class TechniqueDef implements Savable { /** * Returns the {@link DefineList} for the preset defines. - * + * * @return the {@link DefineList} for the preset defines. - * - * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object) + * + * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object) */ public DefineList getShaderPresetDefines() { return presetDefines; } - + /** - * Adds a preset define. + * Adds a preset define. *

    * Preset defines do not depend upon any parameters to be activated, * they are always passed to the shader as long as this technique is used. - * + * * @param defineName The name of the define parameter, e.g. USE_LIGHTING - * @param type The type of the define. See + * @param type The type of the define. See * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } * to see why it matters. - * + * * @param value The value of the define */ public void addShaderPresetDefine(String defineName, VarType type, Object value){ @@ -346,18 +369,18 @@ public class TechniqueDef implements Savable { /** * Returns the name of the fragment shader used by the technique, or null * if no fragment shader is specified. - * + * * @return the name of the fragment shader to be used. */ public String getFragmentShaderName() { return shaderNames.get(Shader.ShaderType.Fragment); } - + /** * Returns the name of the vertex shader used by the technique, or null * if no vertex shader is specified. - * + * * @return the name of the vertex shader to be used. */ public String getVertexShaderName() { @@ -370,7 +393,7 @@ public class TechniqueDef implements Savable { public String getFragmentShaderLanguage() { return shaderLanguages.get(Shader.ShaderType.Fragment); } - + /** * Returns the language of the vertex shader used in this technique. */ @@ -390,10 +413,10 @@ public class TechniqueDef implements Savable { public String getShaderProgramName(Shader.ShaderType shaderType){ return shaderNames.get(shaderType); } - + /** * Adds a new world parameter by the given name. - * + * * @param name The world parameter to add. * @return True if the world parameter name was found and added * to the list of world parameters, false otherwise. @@ -402,7 +425,7 @@ public class TechniqueDef implements Savable { if (worldBinds == null){ worldBinds = new ArrayList(); } - + try { worldBinds.add( UniformBinding.valueOf(name) ); return true; @@ -418,11 +441,11 @@ public class TechniqueDef implements Savable { public void setForcedRenderState(RenderState forcedRenderState) { this.forcedRenderState = forcedRenderState; } - + /** * Returns a list of world parameters that are used by this * technique definition. - * + * * @return The list of world parameters */ public List getWorldBindings() { @@ -448,10 +471,11 @@ public class TechniqueDef implements Savable { oc.write(lightMode, "lightMode", LightMode.Disable); oc.write(shadowMode, "shadowMode", ShadowMode.Disable); oc.write(renderState, "renderState", null); + oc.write(noRender, "noRender", false); oc.write(usesNodes, "usesNodes", false); oc.writeSavableArrayList((ArrayList)shaderNodes,"shaderNodes", null); oc.write(shaderGenerationInfo, "shaderGenerationInfo", null); - + // TODO: Finish this when Map export is available // oc.write(defineParams, "defineParams", null); // TODO: Finish this when List export is available @@ -470,7 +494,8 @@ public class TechniqueDef implements Savable { lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable); shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable); renderState = (RenderState) ic.readSavable("renderState", null); - + noRender = ic.readBoolean("noRender", false); + if (ic.getSavableVersion(TechniqueDef.class) == 0) { // Old version shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("shaderLang", null)); @@ -483,7 +508,7 @@ public class TechniqueDef implements Savable { shaderLanguages.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlLanguage", null)); shaderLanguages.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalLanguage", null)); } - + usesNodes = ic.readBoolean("usesNodes", false); shaderNodes = ic.readSavableArrayList("shaderNodes", null); shaderGenerationInfo = (ShaderGenerationInfo) ic.readSavable("shaderGenerationInfo", null); @@ -525,6 +550,6 @@ public class TechniqueDef implements Savable { //todo: make toString return something usefull @Override public String toString() { - return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}'; - } + return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + ", noRender=" + noRender + '}'; + } } 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 0c81c3e14..d0620ca59 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 @@ -63,7 +63,7 @@ public class J3MLoader implements AssetLoader { // private ErrorLogger errors; private ShaderNodeLoaderDelegate nodesLoaderDelegate; boolean isUseNodes = false; - + private AssetManager assetManager; private AssetKey key; @@ -168,7 +168,7 @@ public class J3MLoader implements AssetLoader { if (tex != null){ if (repeat){ tex.setWrap(WrapMode.Repeat); - } + } }else{ tex = new Texture2D(PlaceholderAssets.getPlaceholderImage(assetManager)); if (repeat){ @@ -176,7 +176,7 @@ public class J3MLoader implements AssetLoader { } tex.setKey(texKey); tex.setName(texKey.getName()); - } + } return tex; }else{ String[] split = value.trim().split(whitespacePattern); @@ -222,15 +222,15 @@ public class J3MLoader implements AssetLoader { } } } - - // [ "(" ")" ] [-LINEAR] [ ":" ] + + // [ "(" ")" ] [-LINEAR] [ ":" ] private void readParam(String statement) throws IOException{ String name; String defaultVal = null; ColorSpace colorSpace = null; - + String[] split = statement.split(":"); - + // Parse default val if (split.length == 1){ // Doesn't contain default value @@ -239,14 +239,14 @@ public class J3MLoader implements AssetLoader { throw new IOException("Parameter statement syntax incorrect"); } statement = split[0].trim(); - defaultVal = split[1].trim(); + defaultVal = split[1].trim(); } - + if (statement.endsWith("-LINEAR")) { colorSpace = ColorSpace.Linear; statement = statement.substring(0, statement.length() - "-LINEAR".length()); } - + // Parse ffbinding int startParen = statement.indexOf("("); if (startParen != -1){ @@ -256,32 +256,32 @@ public class J3MLoader implements AssetLoader { // don't care about bindingStr statement = statement.substring(0, startParen); } - + // Parse type + name split = statement.split(whitespacePattern); if (split.length != 2){ throw new IOException("Parameter statement syntax incorrect"); } - + VarType type; if (split[0].equals("Color")){ type = VarType.Vector4; }else{ type = VarType.valueOf(split[0]); } - + name = split[1]; - + Object defaultValObj = null; - if (defaultVal != null){ + if (defaultVal != null){ defaultValObj = readValue(type, defaultVal); } if(type.isTextureType()){ - materialDef.addMaterialParamTexture(type, name, colorSpace); + materialDef.addMaterialParamTexture(type, name, colorSpace); }else{ materialDef.addMaterialParam(type, name, defaultValObj); } - + } private void readValueParam(String statement) throws IOException{ @@ -376,7 +376,7 @@ public class J3MLoader implements AssetLoader { technique.setRenderState(renderState); renderState = null; } - + private void readForcedRenderState(List renderStates) throws IOException{ renderState = new RenderState(); for (Statement statement : renderStates){ @@ -385,7 +385,7 @@ public class J3MLoader implements AssetLoader { technique.setForcedRenderState(renderState); renderState = null; } - + // [ ":" ] private void readDefine(String statement) throws IOException{ String[] split = statement.split(":"); @@ -405,9 +405,9 @@ public class J3MLoader implements AssetLoader { } } - + private void readTechniqueStatement(Statement statement) throws IOException{ - String[] split = statement.getLine().split("[ \\{]"); + String[] split = statement.getLine().split("[ \\{]"); if (split[0].equals("VertexShader") || split[0].equals("FragmentShader") || split[0].equals("GeometryShader") || @@ -420,12 +420,12 @@ public class J3MLoader implements AssetLoader { readShadowMode(statement.getLine()); }else if (split[0].equals("WorldParameters")){ readWorldParams(statement.getContents()); - }else if (split[0].equals("RenderState")){ + }else if (split[0].equals("RenderState")){ readRenderState(statement.getContents()); - }else if (split[0].equals("ForcedRenderState")){ + }else if (split[0].equals("ForcedRenderState")){ readForcedRenderState(statement.getContents()); - }else if (split[0].equals("Defines")){ - readDefines(statement.getContents()); + }else if (split[0].equals("Defines")){ + readDefines(statement.getContents()); } else if (split[0].equals("ShaderNodesDefinitions")) { initNodesLoader(); if (isUseNodes) { @@ -438,14 +438,16 @@ public class J3MLoader implements AssetLoader { } } else if (split[0].equals("FragmentShaderNodes")) { initNodesLoader(); - if (isUseNodes) { + if (isUseNodes) { nodesLoaderDelegate.readFragmentShaderNodes(statement.getContents()); } + } else if (split[0].equals("NoRender")) { + technique.setNoRender(true); } else { throw new MatParseException(null, split[0], statement); } } - + private void readTransparentStatement(String statement) throws IOException{ String[] split = statement.split(whitespacePattern); if (split.length != 2){ @@ -465,11 +467,11 @@ public class J3MLoader implements AssetLoader { } else { throw new IOException("Technique statement syntax incorrect"); } - + for (Statement statement : techStat.getContents()){ readTechniqueStatement(statement); } - + if(isUseNodes){ nodesLoaderDelegate.computeConditions(); //used for caching later, the shader here is not a file. @@ -479,14 +481,14 @@ public class J3MLoader implements AssetLoader { if (shaderName.containsKey(Shader.ShaderType.Vertex) && shaderName.containsKey(Shader.ShaderType.Fragment)) { technique.setShaderFile(shaderName, shaderLanguage); } - + materialDef.addTechniqueDef(technique); technique = null; shaderLanguage.clear(); shaderName.clear(); } - private void loadFromRoot(List roots) throws IOException{ + private void loadFromRoot(List roots) throws IOException{ if (roots.size() == 2){ Statement exception = roots.get(0); String line = exception.getLine(); @@ -498,7 +500,7 @@ public class J3MLoader implements AssetLoader { }else if (roots.size() != 1){ throw new IOException("Too many roots in J3M/J3MD file"); } - + boolean extending = false; Statement materialStat = roots.get(0); String materialName = materialStat.getLine(); @@ -511,16 +513,16 @@ public class J3MLoader implements AssetLoader { }else{ throw new IOException("Specified file is not a Material file"); } - + String[] split = materialName.split(":", 2); - + if (materialName.equals("")){ - throw new MatParseException("Material name cannot be empty", materialStat); + throw new MatParseException("Material name cannot be empty", materialStat); } if (split.length == 2){ if (!extending){ - throw new MatParseException("Must use 'Material' when extending.", materialStat); + throw new MatParseException("Must use 'Material' when extending.", materialStat); } String extendedMat = split[1].trim(); @@ -535,15 +537,15 @@ public class J3MLoader implements AssetLoader { // material.setAssetName(fileName); }else if (split.length == 1){ if (extending){ - throw new MatParseException("Expected ':', got '{'", materialStat); + throw new MatParseException("Expected ':', got '{'", materialStat); } materialDef = new MaterialDef(assetManager, materialName); // NOTE: pass file name for defs so they can be loaded later materialDef.setAssetName(key.getName()); }else{ - throw new MatParseException("Cannot use colon in material name/path", materialStat); + throw new MatParseException("Cannot use colon in material name/path", materialStat); } - + for (Statement statement : materialStat.getContents()){ split = statement.getLine().split("[ \\{]"); String statType = split[0]; @@ -561,16 +563,16 @@ public class J3MLoader implements AssetLoader { }else if (statType.equals("MaterialParameters")){ readMaterialParams(statement.getContents()); }else{ - throw new MatParseException("Expected material statement, got '"+statType+"'", statement); + throw new MatParseException("Expected material statement, got '"+statType+"'", statement); } } } } - public Object load(AssetInfo info) throws IOException { + public Object load(AssetInfo info) throws IOException { this.assetManager = info.getManager(); - - InputStream in = info.openStream(); + + InputStream in = info.openStream(); try { key = info.getKey(); if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) { @@ -584,7 +586,7 @@ public class J3MLoader implements AssetLoader { in.close(); } } - + if (material != null){ // material implementation return material; @@ -593,7 +595,7 @@ public class J3MLoader implements AssetLoader { return materialDef; } } - + public MaterialDef loadMaterialDef(List roots, AssetManager manager, AssetKey key) throws IOException { this.key = key; this.assetManager = manager; @@ -615,6 +617,6 @@ public class J3MLoader implements AssetLoader { nodesLoaderDelegate.setAssetManager(assetManager); } } - } + } } From 485af7cf2aaf929d15bd39655fe86a0df6fbbb4f Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 3 Jul 2015 22:58:42 +0200 Subject: [PATCH 217/225] TextureFetch shader node now works with glgl1.5 --- .../Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn | 1 + .../resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn index b4ccd3640..69fdec638 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn @@ -2,6 +2,7 @@ ShaderNodeDefinitions{ ShaderNodeDefinition TextureFetch { Type: Fragment Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/texture.frag + Shader GLSL150: Common/MatDefs/ShaderNodes/Basic/texture15.frag Documentation{ Fetches a color value in the given texture acording to given texture coordinates @input texture the texture to read diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag new file mode 100644 index 000000000..b716c3e89 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag @@ -0,0 +1,3 @@ +void main(){ + outColor = texture(texture,texCoord); +} \ No newline at end of file From 6d3377a2a88afa30e54fcc8666a10420e38931ca Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 3 Jul 2015 23:33:19 +0200 Subject: [PATCH 218/225] Fixed Parallax without normal map in lighting.j3md --- .../Common/MatDefs/Light/Lighting.frag | 9 ++++--- .../Common/MatDefs/Light/Lighting.vert | 16 +++++++++-- .../java/jme3test/material/TestParallax.java | 27 +++++++------------ .../Textures/Terrain/BrickWall/BrickWall.j3m | 5 ++-- .../Textures/Terrain/BrickWall/BrickWall2.j3m | 4 +-- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag index 107597047..d2ade5199 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag @@ -37,6 +37,7 @@ varying vec3 SpecularSum; #endif #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) uniform float m_ParallaxHeight; + varying vec3 vViewDirPrlx; #endif #ifdef LIGHTMAP @@ -78,18 +79,18 @@ void main(){ #ifdef STEEP_PARALLAX #ifdef NORMALMAP_PARALLAX //parallax map is stored in the alpha channel of the normal map - newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + newTexCoord = steepParallaxOffset(m_NormalMap, vViewDirPrlx, texCoord, m_ParallaxHeight); #else //parallax map is a texture - newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDirPrlx, texCoord, m_ParallaxHeight); #endif #else #ifdef NORMALMAP_PARALLAX //parallax map is stored in the alpha channel of the normal map - newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + newTexCoord = classicParallaxOffset(m_NormalMap, vViewDirPrlx, texCoord, m_ParallaxHeight); #else //parallax map is a texture - newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDirPrlx, texCoord, m_ParallaxHeight); #endif #endif #else diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert index f92b91fff..df441ed59 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -48,6 +48,10 @@ varying vec3 lightVec; uniform vec4 g_LightDirection; #endif +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + varying vec3 vViewDirPrlx; +#endif + #ifdef USE_REFLECTION uniform vec3 g_CameraPosition; @@ -107,17 +111,25 @@ void main(){ wvLightPos.w = g_LightPosition.w; vec4 lightColor = g_LightColor; - #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + #if (defined(NORMALMAP) || defined(PARALLAXMAP)) && !defined(VERTEX_LIGHTING) vec3 wvTangent = normalize(TransformNormal(modelSpaceTan)); vec3 wvBinormal = cross(wvNormal, wvTangent); mat3 tbnMat = mat3(wvTangent, wvBinormal * inTangent.w,wvNormal); + #endif - vViewDir = -wvPosition * tbnMat; + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vViewDir = -wvPosition * tbnMat; + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) + vViewDirPrlx = vViewDir; + #endif lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz; #elif !defined(VERTEX_LIGHTING) vNormal = wvNormal; vViewDir = viewDir; + #if defined(PARALLAXMAP) + vViewDirPrlx = -wvPosition * tbnMat; + #endif lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); #endif diff --git a/jme3-examples/src/main/java/jme3test/material/TestParallax.java b/jme3-examples/src/main/java/jme3test/material/TestParallax.java index 37b5e09b0..f5af57f1f 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestParallax.java +++ b/jme3-examples/src/main/java/jme3test/material/TestParallax.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2015 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,20 +39,17 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; import com.jme3.math.*; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.filters.FXAAFilter; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.shape.Quad; -import com.jme3.texture.Texture.WrapMode; import com.jme3.util.SkyFactory; import com.jme3.util.TangentBinormalGenerator; public class TestParallax extends SimpleApplication { - private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + private final Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); public static void main(String[] args) { TestParallax app = new TestParallax(); @@ -60,7 +57,7 @@ public class TestParallax extends SimpleApplication { } public void setupSkyBox() { - rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false)); + rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap)); } DirectionalLight dl; @@ -75,12 +72,7 @@ public class TestParallax extends SimpleApplication { public void setupFloor() { mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall2.j3m"); - mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); - mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); - - // Node floorGeom = (Node) assetManager.loadAsset("Models/WaterTest/WaterTest.mesh.xml"); - //Geometry g = ((Geometry) floorGeom.getChild(0)); - //g.getMesh().scaleTextureCoordinates(new Vector2f(10, 10)); + //mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); Node floorGeom = new Node("floorGeom"); Quad q = new Quad(100, 100); @@ -100,9 +92,9 @@ public class TestParallax extends SimpleApplication { public void setupSignpost() { Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); - Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + Material matSp = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); TangentBinormalGenerator.generate(signpost); - signpost.setMaterial(mat); + signpost.setMaterial(matSp); signpost.rotate(0, FastMath.HALF_PI, 0); signpost.setLocalTranslation(12, 23.5f, 30); signpost.setLocalScale(4); @@ -116,7 +108,6 @@ public class TestParallax extends SimpleApplication { cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f)); flyCam.setMoveSpeed(30); - setupLighting(); setupSkyBox(); setupFloor(); @@ -124,13 +115,14 @@ public class TestParallax extends SimpleApplication { inputManager.addListener(new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if ("heightUP".equals(name)) { - parallaxHeigh += 0.0001; + parallaxHeigh += 0.01; mat.setFloat("ParallaxHeight", parallaxHeigh); } if ("heightDown".equals(name)) { - parallaxHeigh -= 0.0001; + parallaxHeigh -= 0.01; parallaxHeigh = Math.max(parallaxHeigh, 0); mat.setFloat("ParallaxHeight", parallaxHeigh); } @@ -142,6 +134,7 @@ public class TestParallax extends SimpleApplication { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (isPressed && "toggleSteep".equals(name)) { steep = !steep; diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m index 8b54f9e39..41af10431 100644 --- a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m +++ b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m @@ -1,8 +1,7 @@ Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { MaterialParameters { Shininess: 2.0 - DiffuseMap : Textures/Terrain/BrickWall/BrickWall.jpg - NormalMap : Textures/Terrain/BrickWall/BrickWall_normal.jpg - ParallaxMap : Textures/Terrain/BrickWall/BrickWall_height.jpg + DiffuseMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg + ParallaxMap : Repeat Textures/Terrain/BrickWall/BrickWall_height.jpg } } \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m index 7db3ab893..8f90a9454 100644 --- a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m +++ b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m @@ -1,8 +1,8 @@ Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { MaterialParameters { Shininess: 2.0 - DiffuseMap : Textures/Terrain/BrickWall/BrickWall.jpg - NormalMap : Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds + DiffuseMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg + NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds PackedNormalParallax: true } } \ No newline at end of file From 9883a18d85cddd916f8b03808343b63e83819551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Bouquet?= Date: Sat, 4 Jul 2015 18:58:12 +0200 Subject: [PATCH 219/225] Update CONTRIBUTING.md --- CONTRIBUTING.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebecab28b..82ffeb4e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,13 +16,6 @@ When you're ready to submit your code, just make a [pull request](https://help.g - When committing, always be sure to run an update before you commit. If there is a conflict between the latest revision and your patch after the update, then it is your responsibility to track down the update that caused the conflict and determine the issue (and fix it). In the case where the breaking commit has no thread linked (and one cannot be found in the forum), then the contributor should contact an administrator and wait for feedback before committing. - If your code is committed and it introduces new functionality, please edit the wiki accordingly. We can easily roll back to previous revisions, so just do your best; point us to it and we’ll see if it sticks! -**Note to Eclipse users:** The Eclipse [git client does not support https](http://hub.jmonkeyengine.org/forum/topic/problem-cloning-the-new-git-repository/#post-265594). The current workaround is to use the command line to clone the repository. -To import the local repository as a project follow these steps: - -1. Add a line 'apply plugin: eclipse' to your common.gradle file in the main project directory. -2. Navigate to the project directory in command line and execute command 'gradle eclipse'. This will load all the dependancies for eclipse. -3. In Eclipse, add the repository as an existing Java Project. - p.s. We will try hold ourselves to a [certain standard](http://www.defmacro.org/2013/04/03/issue-etiquette.html) when it comes to GitHub etiquette. If at any point we fail to uphold this standard, let us know. #### Core Contributors From dca050b96b4e93b79dcc261f52c3436c11152a35 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sat, 4 Jul 2015 18:51:02 +0200 Subject: [PATCH 220/225] Added a protected modifier to getReceivers in Abstract shadow renderer see issue https://github.com/jMonkeyEngine/jmonkeyengine/issues/212 --- .../src/main/java/com/jme3/shadow/AbstractShadowRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java index 5ff4ac8a7..bd70465cb 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -459,7 +459,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable debug = true; } - abstract void getReceivers(GeometryList lightReceivers); + protected abstract void getReceivers(GeometryList lightReceivers); public void postFrame(FrameBuffer out) { if (skipPostPass) { From 2b8898b7b2e05a8700a584bb363e77ee225ffe84 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sat, 4 Jul 2015 19:05:57 +0200 Subject: [PATCH 221/225] Here is the rest of last commit --- .../java/com/jme3/shadow/DirectionalLightShadowRenderer.java | 2 +- .../src/main/java/com/jme3/shadow/PointLightShadowRenderer.java | 2 +- .../src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java index d0189e854..acf8a9677 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java @@ -192,7 +192,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { } @Override - void getReceivers(GeometryList lightReceivers) { + protected void getReceivers(GeometryList lightReceivers) { if (lightReceivers.size()==0) { for (Spatial scene : viewPort.getScenes()) { ShadowUtil.getGeometriesInCamFrustum(scene, viewPort.getCamera(), RenderQueue.ShadowMode.Receive, lightReceivers); diff --git a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java index 2976a2460..f4a6455f1 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java @@ -139,7 +139,7 @@ public class PointLightShadowRenderer extends AbstractShadowRenderer { } @Override - void getReceivers(GeometryList lightReceivers) { + protected void getReceivers(GeometryList lightReceivers) { lightReceivers.clear(); for (Spatial scene : viewPort.getScenes()) { ShadowUtil.getLitGeometriesInViewPort(scene, viewPort.getCamera(), shadowCams, RenderQueue.ShadowMode.Receive, lightReceivers); diff --git a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java index dafd25ac4..b291e722e 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java @@ -151,7 +151,7 @@ public class SpotLightShadowRenderer extends AbstractShadowRenderer { } @Override - void getReceivers(GeometryList lightReceivers) { + protected void getReceivers(GeometryList lightReceivers) { lightReceivers.clear(); Camera[] cameras = new Camera[1]; cameras[0] = shadowCam; From 6d1ab7af6590f3654f5349367ad2e87ed1162709 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 5 Jul 2015 23:35:34 +0200 Subject: [PATCH 222/225] Redesign of the frag part of unshadedNodes so it's more modular, and can be properly used as GLSL 1.5 shader. Added a stress test for unshadedNodes. Changed the name of the texture parameter to textureMap in TextureFetch shaderNode as it was conflicting with the texture function used to fetch a texel from a a texture in glsl 1.5 --- .../Common/MatDefs/Misc/UnshadedNodes.j3md | 216 ++++++++++-------- .../ShaderNodes/Basic/TextureFetch.j3sn | 4 +- .../MatDefs/ShaderNodes/Basic/texture.frag | 2 +- .../MatDefs/ShaderNodes/Basic/texture15.frag | 2 +- .../stress/TestShaderNodesStress.java | 102 +++++++++ 5 files changed, 226 insertions(+), 100 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md index f9fb05288..b35c17a9d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md @@ -1,97 +1,121 @@ -MaterialDef UnshadedNodes { - - MaterialParameters { - Texture2D ColorMap - Texture2D LightMap - Color Color (Color) - Boolean VertexColor (UseVertexColor) - Boolean SeparateTexCoord - - // Alpha threshold for fragment discarding - Float AlphaDiscardThreshold (AlphaTestFallOff) - - // For hardware skinning - Int NumberOfBones - Matrix4Array BoneMatrices - - } - - Technique { - - WorldParameters { - WorldViewProjectionMatrix - //used for fog - WorldViewMatrix - } - - VertexShaderNodes{ - ShaderNode GpuSkinning{ - Definition: BasicGPUSkinning : Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn - Condition : NumberOfBones - InputMapping{ - modelPosition = Global.position; - boneMatrices = MatParam.BoneMatrices - boneWeight = Attr.inHWBoneWeight - boneIndex = Attr.inHWBoneIndex - } - OutputMapping{ - Global.position = modModelPosition - } - } - ShaderNode UnshadedVert{ - Definition: CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn - InputMapping{ - worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix - modelPosition = Global.position.xyz - texCoord1 = Attr.inTexCoord: ColorMap || (LightMap && !SeparateTexCoord) - texCoord2 = Attr.inTexCoord2: SeparateTexCoord - vertColor = Attr.inColor: VertexColor - } - OutputMapping{ - Global.position = projPosition - } - } - } - FragmentShaderNodes{ - ShaderNode UnshadedFrag{ - Definition: Unshaded : Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn - InputMapping{ - texCoord = UnshadedVert.texCoord1: ColorMap - vertColor = UnshadedVert.vertColor: VertexColor - matColor = MatParam.Color: Color - colorMap = MatParam.ColorMap: ColorMap - color = Global.outColor - } - OutputMapping{ - Global.outColor = color - } - } - - ShaderNode AlphaDiscardThreshold{ - Definition: AlphaDiscard : Common/MatDefs/ShaderNodes/Basic/AlphaDiscard.j3sn - Condition : AlphaDiscardThreshold - InputMapping{ - alpha = Global.outColor.a - threshold = MatParam.AlphaDiscardThreshold - } - } - ShaderNode LightMap{ - Definition: LightMapping : Common/MatDefs/ShaderNodes/LightMapping/LightMapping.j3sn - Condition: LightMap - InputMapping{ - texCoord = UnshadedVert.texCoord1: !SeparateTexCoord - texCoord = UnshadedVert.texCoord2: SeparateTexCoord - lightMap = MatParam.LightMap - color = Global.outColor - } - OutputMapping{ - Global.outColor = color - } - } - - } - - } - - +MaterialDef UnshadedNodes { + MaterialParameters { + Texture2D ColorMap + Texture2D LightMap + Color Color (Color) + Boolean VertexColor (UseVertexColor) + Boolean SeparateTexCoord + Float AlphaDiscardThreshold (AlphaTestFallOff) + Int NumberOfBones + Matrix4Array BoneMatrices + } + Technique { + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + } + VertexShaderNodes { + ShaderNode GpuSkinning { + Definition : BasicGPUSkinning : Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn + Condition : NumberOfBones + InputMapping { + modelPosition = Global.position + boneMatrices = MatParam.BoneMatrices + boneWeight = Attr.inHWBoneWeight + boneIndex = Attr.inHWBoneIndex + } + OutputMapping { + Global.position = modModelPosition + } + } + ShaderNode UnshadedVert { + Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn + InputMapping { + worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix + modelPosition = Global.position.xyz + texCoord1 = Attr.inTexCoord : ColorMap || (LightMap && !SeparateTexCoord) + texCoord2 = Attr.inTexCoord2 : SeparateTexCoord + vertColor = Attr.inColor : VertexColor + } + OutputMapping { + Global.position = projPosition + } + } + } + FragmentShaderNodes { + ShaderNode MatColorMult { + Definition : ColorMult : Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn + InputMappings { + color1 = MatParam.Color + color2 = Global.outColor + } + OutputMappings { + Global.outColor = outColor + } + Condition : Color + } + ShaderNode VertColorMult { + Definition : ColorMult : Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn + InputMappings { + color1 = UnshadedVert.vertColor + color2 = Global.outColor + } + OutputMappings { + Global.outColor = outColor + } + Condition : VertexColor + } + ShaderNode ColorMapTF { + Definition : TextureFetch : Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn + InputMappings { + texCoord = UnshadedVert.texCoord1 + textureMap = MatParam.ColorMap + } + OutputMappings { + } + Condition : ColorMap + } + ShaderNode ColorMapMult { + Definition : ColorMult : Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn + InputMappings { + color1 = ColorMapTF.outColor + color2 = Global.outColor + } + OutputMappings { + Global.outColor = outColor + } + Condition : ColorMap + } + ShaderNode AlphaDiscardThreshold { + Definition : AlphaDiscard : Common/MatDefs/ShaderNodes/Basic/AlphaDiscard.j3sn + Condition : AlphaDiscardThreshold + InputMapping { + alpha = Global.outColor.a + threshold = MatParam.AlphaDiscardThreshold + } + } + ShaderNode LightMapTF { + Definition : TextureFetch : Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn + InputMappings { + textureMap = MatParam.LightMap + texCoord = UnshadedVert.texCoord2 : SeparateTexCoord + texCoord = UnshadedVert.texCoord1 : !SeparateTexCoord + } + OutputMappings { + } + Condition : LightMap + } + ShaderNode LightMapMult { + Definition : ColorMult : Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn + OutputMappings { + Global.outColor = outColor + } + InputMappings { + color1 = LightMapTF.outColor + color2 = Global.outColor + } + Condition : LightMap + } + } + } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn index 69fdec638..034fcd116 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn @@ -5,12 +5,12 @@ ShaderNodeDefinitions{ Shader GLSL150: Common/MatDefs/ShaderNodes/Basic/texture15.frag Documentation{ Fetches a color value in the given texture acording to given texture coordinates - @input texture the texture to read + @input textureMap the texture to read @input texCoord the texture coordinates @output outColor the fetched color } Input { - sampler2D texture + sampler2D textureMap vec2 texCoord } Output { diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag index eb83a7b1f..163fbc123 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag @@ -1,3 +1,3 @@ void main(){ - outColor = texture2D(texture,texCoord); + outColor = texture2D(textureMap,texCoord); } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag index b716c3e89..2ece0a646 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture15.frag @@ -1,3 +1,3 @@ void main(){ - outColor = texture(texture,texCoord); + outColor = texture(textureMap,texCoord); } \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java b/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java new file mode 100644 index 000000000..3364f5feb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java @@ -0,0 +1,102 @@ +package jme3test.stress; + +import com.jme3.app.BasicProfilerState; +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.AppStep; +import com.jme3.profile.VpStep; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestShaderNodesStress extends SimpleApplication { + + public static void main(String[] args) { + TestShaderNodesStress app = new TestShaderNodesStress(); + app.start(); + } + + @Override + public void simpleInitApp() { + + Quad q = new Quad(1, 1); + Geometry g = new Geometry("quad", q); + g.setLocalTranslation(-500, -500, 0); + g.setLocalScale(1000); + + rootNode.attachChild(g); + cam.setLocation(new Vector3f(0.0f, 0.0f, 0.40647888f)); + cam.setRotation(new Quaternion(0.0f, 1.0f, 0.0f, 0.0f)); + + Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md"); + //Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + + mat.setColor("Color", ColorRGBA.Yellow); + mat.setTexture("ColorMap", tex); + g.setMaterial(mat); + //place the geoms in the transparent bucket so that they are rendered back to front for maximum overdraw + g.setQueueBucket(RenderQueue.Bucket.Transparent); + + for (int i = 0; i < 1000; i++) { + Geometry cl = g.clone(false); + cl.move(0, 0, -(i + 1)); + rootNode.attachChild(cl); + } + + flyCam.setMoveSpeed(20); + Logger.getLogger("com.jme3").setLevel(Level.WARNING); + + this.setAppProfiler(new Profiler()); + + } + + private class Profiler implements AppProfiler { + + private long startTime; + private long updateTime; + private long renderTime; + private long sum; + private int nbFrames; + + @Override + public void appStep(AppStep step) { + + switch (step) { + case BeginFrame: + startTime = System.nanoTime(); + break; + case RenderFrame: + updateTime = System.nanoTime(); + // System.err.println("Update time : " + (updateTime - startTime)); + break; + case EndFrame: + nbFrames++; + if (nbFrames >= 150) { + renderTime = System.nanoTime(); + sum += renderTime - updateTime; + System.err.println("render time : " + (renderTime - updateTime)); + System.err.println("Average render time : " + ((float)sum / (float)(nbFrames-150))); + } + break; + + } + + } + + @Override + public void vpStep(VpStep step, ViewPort vp, RenderQueue.Bucket bucket) { + + } + + } +} From 34220640aa1db72ef11343a853c12965f57909fe Mon Sep 17 00:00:00 2001 From: Nehon Date: Mon, 6 Jul 2015 19:01:00 +0200 Subject: [PATCH 223/225] Fixed how model bound were refreshed in BathNode : issue https://github.com/jMonkeyEngine/jmonkeyengine/issues/275 --- .../main/java/com/jme3/scene/BatchNode.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 2d603ac2c..9a920093d 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -119,19 +119,6 @@ public class BatchNode extends GeometryGroupNode { setNeedsFullRebatch(true); } - @Override - public void updateGeometricState() { - if (!children.isEmpty()) { - for (Batch batch : batches.getArray()) { - if (batch.needMeshUpdate) { - batch.geometry.updateModelBound(); - batch.geometry.updateWorldBound(); - batch.needMeshUpdate = false; - } - } - } - super.updateGeometricState(); - } protected Matrix4f getTransformMatrix(Geometry g){ return g.cachedWorldMat; @@ -169,7 +156,7 @@ public class BatchNode extends GeometryGroupNode { nvb.updateData(normBuf); - batch.needMeshUpdate = true; + batch.geometry.updateModelBound(); } } @@ -234,7 +221,7 @@ public class BatchNode extends GeometryGroupNode { batch.geometry.setMesh(m); batch.geometry.getMesh().updateCounts(); - batch.geometry.getMesh().updateBound(); + batch.geometry.updateModelBound(); batches.add(batch); } if (batches.size() > 0) { @@ -747,8 +734,7 @@ public class BatchNode extends GeometryGroupNode { } } } - Geometry geometry; - boolean needMeshUpdate = false; + Geometry geometry; } protected void setNeedsFullRebatch(boolean needsFullRebatch) { From 57dd2748e25bf65a6deba37369eed8ab343d2a83 Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 10 Jul 2015 21:28:42 +0200 Subject: [PATCH 224/225] Tested if a joystick axis is not the nullAxis before assigning action to it. --- .../src/main/java/com/jme3/input/DefaultJoystickAxis.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java index 954451145..8f32c2d6b 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java @@ -72,8 +72,10 @@ public class DefaultJoystickAxis implements JoystickAxis { * @param negativeMapping The mapping to receive events when the axis is positive */ public void assignAxis(String positiveMapping, String negativeMapping){ - inputManager.addMapping(positiveMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, false)); - inputManager.addMapping(negativeMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, true)); + if (axisIndex != -1) { + inputManager.addMapping(positiveMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, false)); + inputManager.addMapping(negativeMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, true)); + } } /** From 95d5fdf9c5b45f1bbd4d52e8e2a17f21ad03faa5 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sat, 11 Jul 2015 21:15:42 +0200 Subject: [PATCH 225/225] Fixed issue https://github.com/jMonkeyEngine/jmonkeyengine/issues/252 This was due to a bug in the code where triangle data were stored after calculation. --- .../main/java/com/jme3/util/TangentBinormalGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java index 0f9aac1fc..5e85378ff 100644 --- a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java @@ -612,9 +612,9 @@ public class TangentBinormalGenerator { normal.normalizeLocal(); return new TriangleData( - tangent, - binormal, - normal); + tangent.clone(), + binormal.clone(), + normal.clone()); } finally { tmp.release(); }

  • ad45fW!rE6;Ix&03(DNd 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