From 7bf1c5f27b714f41b7998110d323b26d1805fea2 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 8 Mar 2024 04:15:42 -0800 Subject: [PATCH 01/57] update Groovy to v3.0.21 (#2212) --- common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.gradle b/common.gradle index 7dcfb4f940..a8aaa69230 100644 --- a/common.gradle +++ b/common.gradle @@ -43,7 +43,7 @@ dependencies { // Adding dependencies here will add the dependencies to each subproject. testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:3.12.4' - testImplementation 'org.codehaus.groovy:groovy-test:3.0.19' + testImplementation 'org.codehaus.groovy:groovy-test:3.0.21' } // Uncomment if you want to see the status of every test that is run and From 88d94b7513825edd5ee7858b0f65091d81d46855 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 8 Mar 2024 04:16:04 -0800 Subject: [PATCH 02/57] build.gradle: don't hardcode the Gradle version here (#2213) --- build.gradle | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build.gradle b/build.gradle index 47530072fe..bb86d0101a 100644 --- a/build.gradle +++ b/build.gradle @@ -282,11 +282,6 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { // } //} -wrapper { - gradleVersion = '7.6.4' -} - - retrolambda { javaVersion JavaVersion.VERSION_1_7 incremental true From e80e1988d31cd0cf1cf4cd6c60330002d6c16e55 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 8 Mar 2024 04:16:20 -0800 Subject: [PATCH 03/57] jme3-examples: add the TestIssue2209 class (#2214) --- .../java/jme3test/light/TestIssue2209.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 jme3-examples/src/main/java/jme3test/light/TestIssue2209.java diff --git a/jme3-examples/src/main/java/jme3test/light/TestIssue2209.java b/jme3-examples/src/main/java/jme3test/light/TestIssue2209.java new file mode 100644 index 0000000000..4b0699de88 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestIssue2209.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 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.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import java.util.logging.Logger; +import jme3test.bullet.TestIssue1125; + +/** + * Test case for JME issue #2209: AssertionError caused by shadow renderer. + * + *

For a valid test, assertions must be enabled. + * + *

If successful, the Oto model will appear. If unsuccessful, the application + * with crash with an {@code AssertionError} in {@code GLRenderer}. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestIssue2209 extends SimpleApplication { + /** + * message logger for debugging this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue1125.class.getName()); + + /** + * Main entry point for the TestIssue2209 application. + */ + public static void main(String[] args) { + new TestIssue2209().start(); + } + + /** + * Initializes this application, adding Oto, a light, and a shadow renderer. + */ + @Override + public void simpleInitApp() { + if (!areAssertionsEnabled()) { + throw new IllegalStateException( + "For a valid test, assertions must be enabled."); + } + + DirectionalLight dl = new DirectionalLight(); + rootNode.addLight(dl); + + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer(assetManager, 4_096, 3); + dlsr.setLight(dl); + viewPort.addProcessor(dlsr); + + Node player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + player.setShadowMode(RenderQueue.ShadowMode.Cast); + rootNode.attachChild(player); + } + + /** + * Tests whether assertions are enabled. + * + * @return true if enabled, otherwise false + */ + private static boolean areAssertionsEnabled() { + boolean enabled = false; + assert enabled = true; // Note: intentional side effect. + + return enabled; + } +} From 1cbdcd344730d7edae96e9d8750d890c875ceaeb Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Sun, 10 Mar 2024 05:52:49 +0100 Subject: [PATCH 04/57] Use precision qualifiers only in GL ES shaders (#2217) --- .../Common/ShaderLib/GLSLCompat.glsllib | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib index 77cb34c8c0..bcd895f60a 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib @@ -1,15 +1,16 @@ -#ifdef FRAGMENT_SHADER - precision highp float; - precision highp int; - precision highp sampler2DArray; - precision highp sampler2DShadow; - precision highp samplerCube; - precision highp sampler3D; - precision highp sampler2D; - #if __VERSION__ >= 310 - precision highp sampler2DMS; - #endif - +#ifdef GL_ES + #ifdef FRAGMENT_SHADER + precision highp float; + precision highp int; + precision highp sampler2DArray; + precision highp sampler2DShadow; + precision highp samplerCube; + precision highp sampler3D; + precision highp sampler2D; + #if __VERSION__ >= 310 + precision highp sampler2DMS; + #endif + #endif #endif #if defined GL_ES From b974f715a8c80b7e243a35f496a13f6991ece52e Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:30:56 -0400 Subject: [PATCH 05/57] added test class (#2218) --- .../anim/TestSingleLayerInfluenceMask.java | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java new file mode 100644 index 0000000000..6ab0902f52 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2024 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.anim.AnimComposer; +import com.jme3.anim.ArmatureMask; +import com.jme3.anim.SingleLayerInfluenceMask; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.action.ClipAction; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +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.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +/** + * Tests {@link SingleLayerInfluenceMask}. + * + * The test runs two simultaneous looping actions on seperate layers. + *

+ * The test fails if the visible dancing action does not + * loop seamlessly when using SingleLayerInfluenceMasks. Note that the action is not + * expected to loop seamlessly when not using SingleLayerArmatureMasks. + *

+ * Press the spacebar to switch between using SingleLayerInfluenceMasks and masks + * provided by {@link ArmatureMask}. + * + * @author codex + */ +public class TestSingleLayerInfluenceMask extends SimpleApplication implements ActionListener { + + private Spatial model; + private AnimComposer anim; + private SkinningControl skin; + private boolean useSLIMask = true; + private BitmapText display; + + public static void main(String[] args) { + TestSingleLayerInfluenceMask app = new TestSingleLayerInfluenceMask(); + app.start(); + } + + @Override + public void simpleInitApp() { + + flyCam.setMoveSpeed(30f); + + 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); + + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + display = new BitmapText(font); + display.setSize(font.getCharSet().getRenderedSize()); + display.setText(""); + display.setLocalTranslation(5, context.getSettings().getHeight()-5, 0); + guiNode.attachChild(display); + + inputManager.addMapping("reset", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "reset"); + + setupModel(); + + } + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(model.getWorldTranslation(), Vector3f.UNIT_Y); + } + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("reset") && isPressed) { + model.removeFromParent(); + setupModel(); + } + } + + private void setupModel() { + + model = assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o"); + AnimMigrationUtils.migrate(model); + anim = model.getControl(AnimComposer.class); + skin = model.getControl(SkinningControl.class); + + if (useSLIMask) { + SingleLayerInfluenceMask walkLayer = new SingleLayerInfluenceMask("idleLayer", anim, skin); + walkLayer.addAll(); + walkLayer.makeLayer(); + SingleLayerInfluenceMask danceLayer = new SingleLayerInfluenceMask("danceLayer", anim, skin); + danceLayer.addAll(); + danceLayer.makeLayer(); + } else { + anim.makeLayer("idleLayer", ArmatureMask.createMask(skin.getArmature(), "Root")); + anim.makeLayer("danceLayer", ArmatureMask.createMask(skin.getArmature(), "Root")); + } + + setSLIMaskInfo(); + useSLIMask = !useSLIMask; + + ClipAction clip = (ClipAction)anim.action("Dance"); + clip.setMaxTransitionWeight(.9f); + ClipAction clip2 = (ClipAction)anim.action("IdleTop"); + clip2.setMaxTransitionWeight(.8f); + + anim.setCurrentAction("Dance", "danceLayer"); + anim.setCurrentAction("IdleTop", "idleLayer"); + + rootNode.attachChild(model); + + } + private void setSLIMaskInfo() { + display.setText("Using SingleLayerInfluenceMasks: "+useSLIMask+"\nPress Spacebar to switch masks"); + } + +} From 4781fbcc5b1fcf5885303ea9c7311c6b6395361c Mon Sep 17 00:00:00 2001 From: SceneMax3D Date: Mon, 11 Mar 2024 21:37:11 +0200 Subject: [PATCH 06/57] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3f377cdfb6..b10e8722cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Version number: Major.Minor.SubMinor (e.g. 3.3.0) -jmeVersion = 3.7.0 +jmeVersion = 3.8.0 # Leave empty to autogenerate # (use -PjmeVersionName="myVersion" from commandline to specify a custom version name ) From ea715d80257c7c2c9a17d0e44ccd86176e5a0dc2 Mon Sep 17 00:00:00 2001 From: SceneMax3D Date: Mon, 11 Mar 2024 21:46:51 +0200 Subject: [PATCH 07/57] Update main.yml --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ef3907e0c..72d48087c1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,7 @@ on: push: branches: - master + - v3.7 - v3.6 - v3.5 - v3.4 From 8a71dce372dc7427e8b317ba0f86602e4836ef56 Mon Sep 17 00:00:00 2001 From: SceneMax3D Date: Wed, 13 Mar 2024 21:41:45 +0200 Subject: [PATCH 08/57] Update build.gradle rollback to gson v2.9.1 due to a crash when loading GLTF model --- jme3-plugins-json-gson/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-plugins-json-gson/build.gradle b/jme3-plugins-json-gson/build.gradle index f7223d35f9..6d22f7a774 100644 --- a/jme3-plugins-json-gson/build.gradle +++ b/jme3-plugins-json-gson/build.gradle @@ -11,6 +11,6 @@ sourceSets { dependencies { api project(':jme3-plugins-json') - api 'com.google.code.gson:gson:2.10.1' + api 'com.google.code.gson:gson:2.9.1' } From 94520781ff2c83a810c5a4709fa2583d09a7d220 Mon Sep 17 00:00:00 2001 From: JNightRide <97632588+JNightRide@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:44:00 -0600 Subject: [PATCH 09/57] Gradle build (zip) (#2235) * lwjgl dependency issues --- jme3-examples/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 7fd7034dae..b5979feceb 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -19,8 +19,8 @@ dependencies { implementation project(':jme3-effects') implementation project(':jme3-jbullet') implementation project(':jme3-jogg') -// implementation project(':jme3-lwjgl') - implementation project(':jme3-lwjgl3') + implementation project(':jme3-lwjgl') +// implementation project(':jme3-lwjgl3') implementation project(':jme3-networking') implementation project(':jme3-niftygui') implementation project(':jme3-plugins') From 3a556b5e1ccb5453e5ba9c08cd7cf4b17039af5c Mon Sep 17 00:00:00 2001 From: JNightRide <97632588+JNightRide@users.noreply.github.com> Date: Sat, 30 Mar 2024 12:11:04 -0600 Subject: [PATCH 10/57] Logic code error (#2238) remove a 'redundancy' in the size depth settings in LwjglCanvas --- jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index e5e13117e2..cdc754f3a0 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -497,7 +497,6 @@ protected void createContext(AppSettings settings) { // This will activate the "effective data" scrubber. showGLDataEffective.set(settings.getBoolean("GLDataEffectiveDebug")); - glData.depthSize = settings.getBitsPerPixel(); glData.alphaSize = settings.getAlphaBits(); glData.sRGB = settings.isGammaCorrection(); // Not compatible with very old devices From 1332547915c2d0e0ca99633ce19f94698abcefd1 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sat, 30 Mar 2024 20:41:10 +0200 Subject: [PATCH 11/57] Fix SkeletonDebugger deserializing (#2228) Fix SkeletonDebugger deserializing --- .../jme3/scene/debug/SkeletonDebugger.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java index f764304c73..8068ae37f8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java @@ -32,11 +32,14 @@ package com.jme3.scene.debug; import com.jme3.animation.Skeleton; +import com.jme3.export.JmeImporter; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.util.clone.Cloner; +import java.io.IOException; import java.util.Map; /** @@ -83,16 +86,20 @@ public SkeletonDebugger(String name, Skeleton skeleton, Map bone wires = new SkeletonWire(skeleton, boneLengths); points = new SkeletonPoints(skeleton, boneLengths); - this.attachChild(new Geometry(name + "_wires", wires)); - this.attachChild(new Geometry(name + "_points", points)); + this.attachChild(new Geometry(getGeometryName("_wires"), wires)); + this.attachChild(new Geometry(getGeometryName("_points"), points)); if (boneLengths != null) { interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths); - this.attachChild(new Geometry(name + "_interwires", interBoneWires)); + this.attachChild(new Geometry(getGeometryName("_interwires"), interBoneWires)); } this.setQueueBucket(Bucket.Transparent); } + private String getGeometryName(String suffix) { + return name + suffix; + } + @Override public void updateLogicalState(float tpf) { super.updateLogicalState(tpf); @@ -132,4 +139,23 @@ public void cloneFields(Cloner cloner, Object original) { this.points = cloner.clone(points); this.interBoneWires = cloner.clone(interBoneWires); } + + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + + // Find our stuff + wires = getMesh("_wires"); + points = getMesh("_points"); + interBoneWires = getMesh("_interwires"); + } + + private T getMesh(String suffix) { + Geometry child = (Geometry)getChild(getGeometryName(suffix)); + if(child != null) { + return (T) child.getMesh(); + } + + return null; + } } \ No newline at end of file From 7d5a10bd83655b2925ac702ae7bc74141ef48e72 Mon Sep 17 00:00:00 2001 From: richardTingle <6330028+richardTingle@users.noreply.github.com> Date: Sat, 30 Mar 2024 20:52:34 +0000 Subject: [PATCH 12/57] #2224 Fix bug where the clipRect would be left in the openGL state leading to surprising (clipped) results when later using GLRenderer#copyFrameBuffer (#2225) --- .../src/main/java/com/jme3/renderer/RenderManager.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index c36c6c2955..df1c42f442 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -1246,6 +1246,12 @@ public void renderViewPort(ViewPort vp, float tpf) { // clear any remaining spatials that were not rendered. clearQueue(vp); + /* + * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results + * if renderer#copyFrameBuffer is used later + */ + renderer.clearClipRect(); + if (prof != null) { prof.vpStep(VpStep.EndRender, vp, null); } @@ -1344,8 +1350,7 @@ public void setRenderFilter(Predicate filter) { /** * Returns the render filter that the RenderManager is currently using * - * @param filter - * the render filter + * @return the render filter */ public Predicate getRenderFilter() { return renderFilter; From ec7f6e1f3304d3a6378393d0bb8c820996eed406 Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:49:51 -0400 Subject: [PATCH 13/57] Logs exception as severe on asset locator instantiation failure (#2232) switched to severe logging --- jme3-core/src/main/java/com/jme3/asset/ImplHandler.java | 5 +---- 1 file changed, 1 insertion(+), 4 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 1b6275d12d..90af79d803 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java +++ b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java @@ -124,10 +124,7 @@ protected T initialValue() { } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) { - logger.log(Level.SEVERE, "Cannot create locator of type {0}, does" - + " the class have an empty and publicly accessible" - + " constructor?", type.getName()); - logger.throwing(type.getName(), "", ex); + logger.log(Level.SEVERE, "An exception occurred while instantiating asset locator: " + type.getName(), ex); } return null; } From c81381a8589d8b6d9fe3f4b2f35cf3a403cbf0d4 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Mon, 6 May 2024 13:30:34 -0700 Subject: [PATCH 14/57] main.yml: stop building with Java 1.8 at GitHub Actions (#2254) --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72d48087c1..b55e749025 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,7 +91,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest,windows-2019,macOS-latest] - jdk: [8, 11, 17] + jdk: [11, 17] include: - os: ubuntu-latest osName: linux @@ -102,8 +102,6 @@ jobs: - os: macOS-latest osName: mac deploy: false - - jdk: 8 - deploy: false - jdk: 11 deploy: false From ff7374b8f1fd7ec79e8867654b47fd9f192038b3 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 7 May 2024 21:55:22 +0300 Subject: [PATCH 15/57] Remove material cache, some materials seem not work when cloned (#2255) --- .../java/com/jme3/scene/plugins/gltf/GltfLoader.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index c24efbadc1..46adacd386 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -619,11 +619,6 @@ protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) thr public Material readMaterial(int materialIndex) throws IOException { assertNotNull(materials, "There is no material defined yet a mesh references one"); - Material material = fetchFromCache("materials", materialIndex, Material.class); - if (material != null) { - return material.clone(); - } - JsonObject matData = materials.get(materialIndex).getAsJsonObject(); JsonObject pbrMat = matData.getAsJsonObject("pbrMetallicRoughness"); @@ -693,10 +688,7 @@ public Material readMaterial(int materialIndex) throws IOException { adapter.setParam("emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture"))); - material = adapter.getMaterial(); - addToCache("materials", materialIndex, material, materials.size()); - - return material; + return adapter.getMaterial(); } public void readCameras() throws IOException { From fa6083fd87c78b7317539aa405eded926cc80e37 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 9 May 2024 12:39:50 -0700 Subject: [PATCH 16/57] solution for issue #2250 (exceptions from Image.setMultiSamples(1)) (#2251) --- .../src/main/java/com/jme3/texture/Image.java | 17 ++-- .../java/com/jme3/texture/TestIssue2250.java | 79 +++++++++++++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java 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 c5c0395021..25518966a2 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -942,14 +942,19 @@ public int getMultiSamples() { * into a multisample texture (on OpenGL3.1 and higher). */ public void setMultiSamples(int multiSamples) { - if (multiSamples <= 0) + if (multiSamples <= 0) { throw new IllegalArgumentException("multiSamples must be > 0"); + } - if (getData(0) != null) - throw new IllegalArgumentException("Cannot upload data as multisample texture"); + if (multiSamples > 1) { + if (getData(0) != null) { + throw new IllegalArgumentException("Cannot upload data as multisample texture"); + } - if (hasMipmaps()) - throw new IllegalArgumentException("Multisample textures do not support mipmaps"); + if (hasMipmaps()) { + throw new IllegalArgumentException("Multisample textures do not support mipmaps"); + } + } this.multiSamples = multiSamples; } diff --git a/jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java b/jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java new file mode 100644 index 0000000000..51d9e9e007 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 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.texture; + +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import org.junit.Test; + +/** + * Verify that setMultiSamples(1) can be applied to any Image. This was issue + * #2250 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue2250 { + + /** + * Test setMultiSamples() on an Image with a data buffer. + */ + @Test + public void testIssue2250WithData() { + int width = 8; + int height = 8; + int numBytes = 4 * width * height; + ByteBuffer data = BufferUtils.createByteBuffer(numBytes); + Image image1 = new Image( + Image.Format.RGBA8, width, height, data, ColorSpace.Linear); + + image1.setMultiSamples(1); + } + + /** + * Test setMultiSamples() on an Image with mip maps. + */ + @Test + public void testIssue2250WithMips() { + int width = 8; + int height = 8; + int depth = 1; + int[] mipMapSizes = {256, 64, 16, 4}; + + ArrayList data = new ArrayList<>(); + Image image2 = new Image(Image.Format.RGBA8, width, height, depth, data, + mipMapSizes, ColorSpace.Linear); + + image2.setMultiSamples(1); + } +} From b5abd2eb37caae9d1b567992ebba635bd7597d21 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 9 May 2024 12:45:31 -0700 Subject: [PATCH 17/57] main.yml: upgrade the wrapper validation action to v3 (#2257) --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b55e749025..0cc3203965 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,7 +70,7 @@ jobs: with: fetch-depth: 1 - name: Validate the Gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v3 - name: Build run: | ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ @@ -124,7 +124,7 @@ jobs: path: build/native - name: Validate the Gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v3 - name: Build Engine shell: bash run: | From 4152c1b2f28e3a1b8acafce25f83d3a14717f7a2 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 11 May 2024 11:55:30 -0700 Subject: [PATCH 18/57] override toString() in certain math classes (#2253) * AbstractTriangle: override the toString() method * Line: override the toString() method * AbstractTriangle: insert 2 more spaces into the toString() result * LineSegment: override the toString() method * Rectangle: override the toString() method * com.jme3.math tests: add the TestToString class --- .../java/com/jme3/math/AbstractTriangle.java | 18 +++- .../src/main/java/com/jme3/math/Line.java | 18 +++- .../main/java/com/jme3/math/LineSegment.java | 19 +++- .../main/java/com/jme3/math/Rectangle.java | 17 +++- .../test/java/com/jme3/math/TestToString.java | 87 +++++++++++++++++++ 5 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 jme3-core/src/test/java/com/jme3/math/TestToString.java diff --git a/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java index 87dc01d51f..df1ef1aa01 100644 --- a/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java +++ b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,4 +79,20 @@ public abstract class AbstractTriangle implements Collidable { public int collideWith(Collidable other, CollisionResults results) { return other.collideWith(this, results); } + + /** + * Returns a string representation of the triangle, which is unaffected. For + * example, a {@link com.jme3.math.Triangle} joining (1,0,0) and (0,1,0) + * with (0,0,1) is represented by: + *

+     * Triangle [V1: (1.0, 0.0, 0.0)  V2: (0.0, 1.0, 0.0)  V3: (0.0, 0.0, 1.0)]
+     * 
+ * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [V1: " + get1() + " V2: " + + get2() + " V3: " + get3() + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Line.java b/jme3-core/src/main/java/com/jme3/math/Line.java index f215dba4d6..122b05225e 100644 --- a/jme3-core/src/main/java/com/jme3/math/Line.java +++ b/jme3-core/src/main/java/com/jme3/math/Line.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -274,4 +274,20 @@ public Line clone() { throw new AssertionError(); } } + + /** + * Returns a string representation of the Line, which is unaffected. For + * example, a line with origin (1,0,0) and direction (0,1,0) is represented + * by: + *
+     * Line [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)]
+     * 
+ * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + + " Direction: " + direction + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/LineSegment.java b/jme3-core/src/main/java/com/jme3/math/LineSegment.java index a1436cd518..a840f1cd09 100644 --- a/jme3-core/src/main/java/com/jme3/math/LineSegment.java +++ b/jme3-core/src/main/java/com/jme3/math/LineSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -720,6 +720,23 @@ public LineSegment clone() { } } + /** + * Returns a string representation of the LineSegment, which is unaffected. + * For example, a segment extending from (1,0,0) to (1,1,0) is represented + * by: + *
+     * LineSegment [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)  Extent: 1.0]
+     * 
+ * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + + " Direction: " + direction + " Extent: " + extent + "]"; + } + + /** /** *

Evaluates whether a given point is contained within the axis aligned bounding box * that contains this LineSegment.

This function is float error aware.

diff --git a/jme3-core/src/main/java/com/jme3/math/Rectangle.java b/jme3-core/src/main/java/com/jme3/math/Rectangle.java index 570e16f896..67cfcb15d7 100644 --- a/jme3-core/src/main/java/com/jme3/math/Rectangle.java +++ b/jme3-core/src/main/java/com/jme3/math/Rectangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -253,4 +253,19 @@ public Rectangle clone() { throw new AssertionError(); } } + + /** + * Returns a string representation of the Recatangle, which is unaffected. + * For example, a rectangle with vertices at (1,0,0), (2,0,0), (1,2,0), and + * (2,2,0) is represented by: + *
+     * Rectangle [A: (1.0, 0.0, 0.0)  B: (2.0, 0.0, 0.0)  C: (1.0, 2.0, 0.0)]
+     * 
+ * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [A: " + a + " B: " + b + " C: " + c + "]"; + } } diff --git a/jme3-core/src/test/java/com/jme3/math/TestToString.java b/jme3-core/src/test/java/com/jme3/math/TestToString.java new file mode 100644 index 0000000000..c30c1e45ed --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/TestToString.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2024 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.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Test various toString() methods using JUnit. See also + * {@link com.jme3.math.TestTransform}. + * + * @author Stephen Gold + */ +public class TestToString { + /** + * Test various {@code toString()} methods against their javadoc. + */ + @Test + public void testToString() { + // Test data that's never modified: + Line line = new Line( + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + + LineSegment segment = new LineSegment( + new Vector3f(1f, 0f, 0f), new Vector3f(0f, 1f, 0f), 1f); + + Rectangle rectangle = new Rectangle( + new Vector3f(1f, 0f, 0f), + new Vector3f(2f, 0f, 0f), + new Vector3f(1f, 2f, 0f)); + + Triangle triangle = new Triangle( + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + new Vector3f(0f, 0f, 1f)); + + // Verify that the methods don't throw an exception: + String lineString = line.toString(); + String segmentString = segment.toString(); + String rectangleString = rectangle.toString(); + String triangleString = triangle.toString(); + + // Verify that the results match the javadoc: + Assert.assertEquals( + "Line [Origin: (1.0, 0.0, 0.0) Direction: (0.0, 1.0, 0.0)]", + lineString); + Assert.assertEquals( + "LineSegment [Origin: (1.0, 0.0, 0.0) Direction: (0.0, 1.0, 0.0) Extent: 1.0]", + segmentString); + Assert.assertEquals( + "Rectangle [A: (1.0, 0.0, 0.0) B: (2.0, 0.0, 0.0) C: (1.0, 2.0, 0.0)]", + rectangleString); + Assert.assertEquals( + "Triangle [V1: (1.0, 0.0, 0.0) V2: (0.0, 1.0, 0.0) V3: (0.0, 0.0, 1.0)]", + triangleString); + } +} From 76d8a43297dccdb2146bc2bd49dbed60198c627a Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Wed, 15 May 2024 12:26:50 -0400 Subject: [PATCH 19/57] Added SoftBloomFilter (#2229) * added pbr bloom filter * add javadoc and license * documented and tweaked test * added exception * various formatting fixes * fixed javadoc typo * fixed bug on applying glow factor * fix javadoc typo * fixed formatting issues * switched texture min/mag filters * rename filter * rename filter * improved test and capped number of passes * reformat test * serialize bilinear filtering * delete unrelated files * increase size limit to 2 * renamed shaders --- .../jme3/post/filters/SoftBloomFilter.java | 337 ++++++++++++++++++ .../Common/MatDefs/Post/Downsample.frag | 60 ++++ .../Common/MatDefs/Post/Downsample.j3md | 22 ++ .../Common/MatDefs/Post/SoftBloomFinal.frag | 15 + .../Common/MatDefs/Post/SoftBloomFinal.j3md | 23 ++ .../Common/MatDefs/Post/Upsample.frag | 46 +++ .../Common/MatDefs/Post/Upsample.j3md | 23 ++ .../main/java/jme3test/post/TestBloom.java | 2 - .../java/jme3test/post/TestSoftBloom.java | 253 +++++++++++++ 9 files changed, 779 insertions(+), 2 deletions(-) create mode 100644 jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md create mode 100644 jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java new file mode 100644 index 0000000000..3ccbf03ff9 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2024 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.post.filters; + +import com.jme3.asset.AssetManager; +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.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import java.io.IOException; +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.LinkedList; + +/** + * Adds a glow effect to the scene. + *

+ * Compared to {@link BloomFilter}, this filter produces much higher quality + * results that feel much more natural. + *

+ * This implementation, unlike BloomFilter, has no brightness threshold, + * meaning all aspects of the scene glow, although only very bright areas will + * noticeably produce glow. For this reason, this filter should only be used + * if HDR is also being utilized, otherwise BloomFilter should be preferred. + *

+ * This filter uses the PBR bloom algorithm presented in + * this article. + * + * @author codex + */ +public class SoftBloomFilter extends Filter { + + private static final Logger logger = Logger.getLogger(SoftBloomFilter.class.getName()); + + private AssetManager assetManager; + private RenderManager renderManager; + private ViewPort viewPort; + private int width; + private int height; + private Pass[] downsamplingPasses; + private Pass[] upsamplingPasses; + private final Image.Format format = Image.Format.RGBA16F; + private boolean initialized = false; + private int numSamplingPasses = 5; + private float glowFactor = 0.05f; + private boolean bilinearFiltering = true; + + /** + * Creates filter with default settings. + */ + public SoftBloomFilter() { + super("SoftBloomFilter"); + } + + @Override + protected void initFilter(AssetManager am, RenderManager rm, ViewPort vp, int w, int h) { + + assetManager = am; + renderManager = rm; + viewPort = vp; + postRenderPasses = new LinkedList<>(); + Renderer renderer = renderManager.getRenderer(); + this.width = w; + this.height = h; + + capPassesToSize(w, h); + + downsamplingPasses = new Pass[numSamplingPasses]; + upsamplingPasses = new Pass[numSamplingPasses]; + + // downsampling passes + Material downsampleMat = new Material(assetManager, "Common/MatDefs/Post/Downsample.j3md"); + Vector2f initTexelSize = new Vector2f(1f/w, 1f/h); + w = w >> 1; h = h >> 1; + Pass initialPass = new Pass() { + @Override + public boolean requiresSceneAsTexture() { + return true; + } + @Override + public void beforeRender() { + downsampleMat.setVector2("TexelSize", initTexelSize); + } + }; + initialPass.init(renderer, w, h, format, Image.Format.Depth, 1, downsampleMat); + postRenderPasses.add(initialPass); + downsamplingPasses[0] = initialPass; + for (int i = 1; i < downsamplingPasses.length; i++) { + Vector2f texelSize = new Vector2f(1f/w, 1f/h); + w = w >> 1; h = h >> 1; + Pass prev = downsamplingPasses[i-1]; + Pass pass = new Pass() { + @Override + public void beforeRender() { + downsampleMat.setTexture("Texture", prev.getRenderedTexture()); + downsampleMat.setVector2("TexelSize", texelSize); + } + }; + pass.init(renderer, w, h, format, Image.Format.Depth, 1, downsampleMat); + if (bilinearFiltering) { + pass.getRenderedTexture().setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + } + postRenderPasses.add(pass); + downsamplingPasses[i] = pass; + } + + // upsampling passes + Material upsampleMat = new Material(assetManager, "Common/MatDefs/Post/Upsample.j3md"); + for (int i = 0; i < upsamplingPasses.length; i++) { + Vector2f texelSize = new Vector2f(1f/w, 1f/h); + w = w << 1; h = h << 1; + Pass prev; + if (i == 0) { + prev = downsamplingPasses[downsamplingPasses.length-1]; + } else { + prev = upsamplingPasses[i-1]; + } + Pass pass = new Pass() { + @Override + public void beforeRender() { + upsampleMat.setTexture("Texture", prev.getRenderedTexture()); + upsampleMat.setVector2("TexelSize", texelSize); + } + }; + pass.init(renderer, w, h, format, Image.Format.Depth, 1, upsampleMat); + if (bilinearFiltering) { + pass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear); + } + postRenderPasses.add(pass); + upsamplingPasses[i] = pass; + } + + material = new Material(assetManager, "Common/MatDefs/Post/SoftBloomFinal.j3md"); + material.setTexture("GlowMap", upsamplingPasses[upsamplingPasses.length-1].getRenderedTexture()); + material.setFloat("GlowFactor", glowFactor); + + initialized = true; + + } + + @Override + protected Material getMaterial() { + return material; + } + + /** + * Sets the number of sampling passes in each step. + *

+ * Higher values produce more glow with higher resolution, at the cost + * of more passes. Lower values produce less glow with lower resolution. + *

+ * The total number of passes is {@code 2n+1}: n passes for downsampling + * (13 texture reads per pass per fragment), n passes for upsampling and blur + * (9 texture reads per pass per fragment), and 1 pass for blending (2 texture reads + * per fragment). Though, it should be noted that for each downsampling pass the + * number of fragments decreases by 75%, and for each upsampling pass, the number + * of fragments quadruples (which restores the number of fragments to the original + * resolution). + *

+ * Setting this after the filter has been initialized forces reinitialization. + *

+ * default=5 + * + * @param numSamplingPasses The number of passes per donwsampling/upsampling step. Must be greater than zero. + * @throws IllegalArgumentException if argument is less than or equal to zero + */ + public void setNumSamplingPasses(int numSamplingPasses) { + if (numSamplingPasses <= 0) { + throw new IllegalArgumentException("Number of sampling passes must be greater than zero (found: " + numSamplingPasses + ")."); + } + if (this.numSamplingPasses != numSamplingPasses) { + this.numSamplingPasses = numSamplingPasses; + if (initialized) { + initFilter(assetManager, renderManager, viewPort, width, height); + } + } + } + + /** + * Sets the factor at which the glow result texture is merged with + * the scene texture. + *

+ * Low values favor the scene texture more, while high values make + * glow more noticeable. This value is clamped between 0 and 1. + *

+ * default=0.05f + * + * @param factor + */ + public void setGlowFactor(float factor) { + this.glowFactor = FastMath.clamp(factor, 0, 1); + if (material != null) { + material.setFloat("GlowFactor", glowFactor); + } + } + + /** + * Sets pass textures to use bilinear filtering. + *

+ * If true, downsampling textures are set to {@code min=BilinearNoMipMaps} and + * upsampling textures are set to {@code mag=Bilinear}, which produces better + * quality glow. If false, textures use their default filters. + *

+ * default=true + * + * @param bilinearFiltering true to use bilinear filtering + */ + public void setBilinearFiltering(boolean bilinearFiltering) { + if (this.bilinearFiltering != bilinearFiltering) { + this.bilinearFiltering = bilinearFiltering; + if (initialized) { + for (Pass p : downsamplingPasses) { + if (this.bilinearFiltering) { + p.getRenderedTexture().setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + } else { + p.getRenderedTexture().setMinFilter(Texture.MinFilter.NearestNoMipMaps); + } + } + for (Pass p : upsamplingPasses) { + if (this.bilinearFiltering) { + p.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear); + } else { + p.getRenderedTexture().setMagFilter(Texture.MagFilter.Nearest); + } + } + } + } + } + + /** + * Gets the number of downsampling/upsampling passes per step. + * + * @return number of downsampling/upsampling passes + * @see #setNumSamplingPasses(int) + */ + public int getNumSamplingPasses() { + return numSamplingPasses; + } + + /** + * Gets the glow factor. + * + * @return glow factor + * @see #setGlowFactor(float) + */ + public float getGlowFactor() { + return glowFactor; + } + + /** + * Returns true if pass textures use bilinear filtering. + * + * @return + * @see #setBilinearFiltering(boolean) + */ + public boolean isBilinearFiltering() { + return bilinearFiltering; + } + + /** + * Caps the number of sampling passes so that texture size does + * not go below 1 on any axis. + *

+ * A message will be logged if the number of sampling passes is changed. + * + * @param w texture width + * @param h texture height + */ + private void capPassesToSize(int w, int h) { + int limit = Math.min(w, h); + for (int i = 0; i < numSamplingPasses; i++) { + limit = limit >> 1; + if (limit <= 2) { + numSamplingPasses = i; + logger.log(Level.INFO, "Number of sampling passes capped at {0} due to texture size.", i); + break; + } + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(numSamplingPasses, "numSamplingPasses", 5); + oc.write(glowFactor, "glowFactor", 0.05f); + oc.write(bilinearFiltering, "bilinearFiltering", true); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + numSamplingPasses = ic.readInt("numSamplingPasses", 5); + glowFactor = ic.readFloat("glowFactor", 0.05f); + bilinearFiltering = ic.readBoolean("bilinearFiltering", true); + } + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag new file mode 100644 index 0000000000..2803a95d87 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag @@ -0,0 +1,60 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform vec2 m_TexelSize; +varying vec2 texCoord; + +void main() { + + // downsampling code: https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom + + float x = m_TexelSize.x; + float y = m_TexelSize.y; + + // Take 13 samples around current texel + // a - b - c + // - j - k - + // d - e - f + // - l - m - + // g - h - i + // === ('e' is the current texel) === + vec3 a = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y + 2*y)).rgb; + vec3 b = getColor(m_Texture, vec2(texCoord.x, texCoord.y + 2*y)).rgb; + vec3 c = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y + 2*y)).rgb; + + vec3 d = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y)).rgb; + vec3 e = getColor(m_Texture, vec2(texCoord.x, texCoord.y)).rgb; + vec3 f = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y)).rgb; + + vec3 g = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y - 2*y)).rgb; + vec3 h = getColor(m_Texture, vec2(texCoord.x, texCoord.y - 2*y)).rgb; + vec3 i = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y - 2*y)).rgb; + + vec3 j = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y + y)).rgb; + vec3 k = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y + y)).rgb; + vec3 l = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y - y)).rgb; + vec3 m = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y - y)).rgb; + + // Apply weighted distribution: + // 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1 + // a,b,d,e * 0.125 + // b,c,e,f * 0.125 + // d,e,g,h * 0.125 + // e,f,h,i * 0.125 + // j,k,l,m * 0.5 + // This shows 5 square areas that are being sampled. But some of them overlap, + // so to have an energy preserving downsample we need to make some adjustments. + // The weights are the distributed, so that the sum of j,k,l,m (e.g.) + // contribute 0.5 to the final color output. The code below is written + // to effectively yield this sum. We get: + // 0.125*5 + 0.03125*4 + 0.0625*4 = 1 + vec3 downsample = e*0.125; + downsample += (a+c+g+i)*0.03125; + downsample += (b+d+f+h)*0.0625; + downsample += (j+k+l+m)*0.125; + + gl_FragColor = vec4(downsample, 1.0); + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md new file mode 100644 index 0000000000..595a918eb3 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md @@ -0,0 +1,22 @@ +MaterialDef Downsample { + + MaterialParameters { + Texture2D Texture + Vector2 TexelSize + Int BoundDrawBuffer + Int NumSamples + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Downsample.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples + } + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag new file mode 100644 index 0000000000..20fb792cc3 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag @@ -0,0 +1,15 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform sampler2D m_GlowMap; +uniform float m_GlowFactor; +varying vec2 texCoord; + +void main() { + + gl_FragColor = mix(getColor(m_Texture, texCoord), texture2D(m_GlowMap, texCoord), m_GlowFactor); + +} + diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md new file mode 100644 index 0000000000..d0597f9f9b --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md @@ -0,0 +1,23 @@ +MaterialDef PBRBloomFinal { + + MaterialParameters { + Texture2D Texture + Texture2D GlowMap + Float GlowFactor : 0.05 + Int BoundDrawBuffer + Int NumSamples + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/SoftBloomFinal.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples + } + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag new file mode 100644 index 0000000000..61bf280061 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag @@ -0,0 +1,46 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform vec2 m_TexelSize; +varying vec2 texCoord; + +void main() { + + // upsampling code: https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom + + // The filter kernel is applied with a radius, specified in texture + // coordinates, so that the radius will vary across mip resolutions. + float x = m_TexelSize.x; + float y = m_TexelSize.y; + + // Take 9 samples around current texel: + // a - b - c + // d - e - f + // g - h - i + // === ('e' is the current texel) === + vec3 a = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y + y)).rgb; + vec3 b = getColor(m_Texture, vec2(texCoord.x, texCoord.y + y)).rgb; + vec3 c = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y + y)).rgb; + + vec3 d = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y)).rgb; + vec3 e = getColor(m_Texture, vec2(texCoord.x, texCoord.y)).rgb; + vec3 f = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y)).rgb; + + vec3 g = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y - y)).rgb; + vec3 h = getColor(m_Texture, vec2(texCoord.x, texCoord.y - y)).rgb; + vec3 i = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y - y)).rgb; + + // Apply weighted distribution, by using a 3x3 tent filter: + // | 1 2 1 | + // 1/16 * | 2 4 2 | + // | 1 2 1 | + vec3 upsample = e*4.0; + upsample += (b+d+f+h)*2.0; + upsample += (a+c+g+i); + upsample /= 16.0; + + gl_FragColor = vec4(upsample, 1.0); + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md new file mode 100644 index 0000000000..2ce2cc9976 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md @@ -0,0 +1,23 @@ +MaterialDef Upsample { + + MaterialParameters { + Texture2D Texture + Vector2 TexelSize + Float FilterRadius : 0.01 + Int BoundDrawBuffer + Int NumSamples + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Upsample.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestBloom.java b/jme3-examples/src/main/java/jme3test/post/TestBloom.java index faef6ac8e7..5608890801 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestBloom.java +++ b/jme3-examples/src/main/java/jme3test/post/TestBloom.java @@ -75,8 +75,6 @@ public void simpleInitApp() { mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); - - Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); matSoil.setFloat("Shininess", 15f); diff --git a/jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java b/jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java new file mode 100644 index 0000000000..e277e8f943 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2024 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.asset.TextureKey; +import com.jme3.environment.EnvironmentProbeControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.SoftBloomFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.util.SkyFactory; +import com.jme3.util.SkyFactory.EnvMapType; + +/** + * Tests {@link SoftBloomFilter} with HDR. + *

+ * Note: the camera is pointed directly at the ground, which is completely + * black for some reason. + * + * @author codex + */ +public class TestSoftBloom extends SimpleApplication implements ActionListener, AnalogListener { + + private SoftBloomFilter bloom; + private BitmapText passes, factor, bilinear; + private BitmapText power, intensity; + private Material tankMat; + private float emissionPower = 50; + private float emissionIntensity = 50; + private final int maxPasses = 10; + private final float factorRate = 0.1f; + + public static void main(String[] args){ + TestSoftBloom app = new TestSoftBloom(); + app.start(); + } + + @Override + public void simpleInitApp() { + + cam.setLocation(new Vector3f(10, 10, 10)); + flyCam.setMoveSpeed(20); + + 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)); + + 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.Gray); + matSoil.setColor("Specular", ColorRGBA.Gray); + + Spatial 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(800, 10, 700)); + soil.setLocalTranslation(0, -13, 550); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(soil); + + tankMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md"); + tankMat.setTexture("BaseColorMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_diffuse.jpg", !true))); + tankMat.setTexture("SpecularMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_specular.jpg", !true))); + tankMat.setTexture("NormalMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_normals.png", !true))); + tankMat.setTexture("EmissiveMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_glow_map.jpg", !true))); + tankMat.setFloat("EmissivePower", emissionPower); + tankMat.setFloat("EmissiveIntensity", 50); + tankMat.setFloat("Metallic", .5f); + Spatial tank = assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + tank.setLocalTranslation(-10, 5, -10); + tank.setMaterial(tankMat); + rootNode.attachChild(tank); + + DirectionalLight light=new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White); + //rootNode.addLight(light); + + PointLight pl = new PointLight(); + pl.setPosition(new Vector3f(5, 5, 5)); + pl.setRadius(1000); + pl.setColor(ColorRGBA.White); + rootNode.addLight(pl); + + // load sky + Spatial sky = SkyFactory.createSky(assetManager, + "Textures/Sky/Bright/FullskiesBlueClear03.dds", + EnvMapType.CubeMap); + sky.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(sky); + EnvironmentProbeControl.tagGlobal(sky); + + rootNode.addControl(new EnvironmentProbeControl(assetManager, 256)); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + bloom = new SoftBloomFilter(); + fpp.addFilter(bloom); + viewPort.addProcessor(fpp); + + int textY = context.getSettings().getHeight()-5; + float xRow1 = 10, xRow2 = 250; + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + passes = createText("", xRow1, textY); + createText("[ R / F ]", xRow2, textY); + factor = createText("", xRow1, textY-25); + createText("[ T / G ]", xRow2, textY-25); + bilinear = createText("", xRow1, textY-25*2); + createText("[ space ]", xRow2, textY-25*2); + power = createText("", xRow1, textY-25*3); + createText("[ Y / H ]", xRow2, textY-25*3); + intensity = createText("", xRow1, textY-25*4); + createText("[ U / J ]", xRow2, textY-25*4); + updateHud(); + + inputManager.addMapping("incr-passes", new KeyTrigger(KeyInput.KEY_R)); + inputManager.addMapping("decr-passes", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addMapping("incr-factor", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("decr-factor", new KeyTrigger(KeyInput.KEY_G)); + inputManager.addMapping("toggle-bilinear", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("incr-power", new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addMapping("decr-power", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("incr-intensity", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("decr-intensity", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(this, "incr-passes", "decr-passes", "incr-factor", "decr-factor", + "toggle-bilinear", "incr-power", "decr-power", "incr-intensity", "decr-intensity"); + + } + + @Override + public void simpleUpdate(float tpf) { + updateHud(); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + if (name.equals("incr-passes")) { + bloom.setNumSamplingPasses(Math.min(bloom.getNumSamplingPasses()+1, maxPasses)); + } else if (name.equals("decr-passes")) { + bloom.setNumSamplingPasses(Math.max(bloom.getNumSamplingPasses()-1, 1)); + } else if (name.equals("toggle-bilinear")) { + bloom.setBilinearFiltering(!bloom.isBilinearFiltering()); + } + updateHud(); + } + } + + @Override + public void onAnalog(String name, float value, float tpf) { + if (name.equals("incr-factor")) { + bloom.setGlowFactor(bloom.getGlowFactor()+factorRate*tpf); + } else if (name.equals("decr-factor")) { + bloom.setGlowFactor(bloom.getGlowFactor()-factorRate*tpf); + } else if (name.equals("incr-power")) { + emissionPower += 10f*tpf; + updateTankMaterial(); + } else if (name.equals("decr-power")) { + emissionPower -= 10f*tpf; + updateTankMaterial(); + } else if (name.equals("incr-intensity")) { + emissionIntensity += 10f*tpf; + updateTankMaterial(); + } else if (name.equals("decr-intensity")) { + emissionIntensity -= 10f*tpf; + updateTankMaterial(); + } + updateHud(); + } + + private BitmapText createText(String string, float x, float y) { + BitmapText text = new BitmapText(guiFont); + text.setSize(guiFont.getCharSet().getRenderedSize()); + text.setLocalTranslation(x, y, 0); + text.setText(string); + guiNode.attachChild(text); + return text; + } + + private void updateHud() { + passes.setText("Passes = " + bloom.getNumSamplingPasses()); + factor.setText("Glow Factor = " + floatToString(bloom.getGlowFactor(), 5)); + bilinear.setText("Bilinear Filtering = " + bloom.isBilinearFiltering()); + power.setText("Emission Power = " + floatToString(emissionPower, 5)); + intensity.setText("Emission Intensity = " + floatToString(emissionIntensity, 5)); + } + + private String floatToString(float value, int length) { + String string = Float.toString(value); + return string.substring(0, Math.min(length, string.length())); + } + + private void updateTankMaterial() { + emissionPower = Math.max(emissionPower, 0); + emissionIntensity = Math.max(emissionIntensity, 0); + tankMat.setFloat("EmissivePower", emissionPower); + tankMat.setFloat("EmissiveIntensity", emissionIntensity); + } + +} From 1d20091deb69cc9a162605eafeb1eb7f11744869 Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Fri, 17 May 2024 23:23:33 +0200 Subject: [PATCH 20/57] MatParamTexture: duplicate variables, missing javadoc, exceptions (#2243) * MatParamTexture: duplicate variables, missing javadoc, setValue to null throws exception This PR solves all the problems listed above. * removal of setValue() override method * javadoc --- .../com/jme3/material/MatParamTexture.java | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 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 187eebc72d..58bd44418d 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,51 +40,67 @@ import com.jme3.texture.image.ColorSpace; import java.io.IOException; +/** + * A material parameter that holds a reference to a texture and its required color space. + * This class extends {@link MatParam} to provide texture specific functionalities. + */ public class MatParamTexture extends MatParam { - private Texture texture; private ColorSpace colorSpace; + /** + * Constructs a new MatParamTexture instance with the specified type, name, + * texture, and color space. + * + * @param type the type of the material parameter + * @param name the name of the parameter + * @param texture the texture associated with this parameter + * @param colorSpace the required color space for the texture + */ public MatParamTexture(VarType type, String name, Texture texture, ColorSpace colorSpace) { super(type, name, texture); - this.texture = texture; this.colorSpace = colorSpace; } + /** + * Serialization only. Do not use. + */ public MatParamTexture() { } + /** + * Retrieves the texture associated with this material parameter. + * + * @return the texture object + */ public Texture getTextureValue() { - return texture; + return (Texture) getValue(); } + /** + * Sets the texture associated with this material parameter. + * + * @param value the texture object to set + * @throws RuntimeException if the provided value is not a {@link Texture} + */ public void setTextureValue(Texture value) { - this.value = value; - this.texture = value; - } - - @Override - public void setValue(Object value) { - if (!(value instanceof Texture)) { - throw new IllegalArgumentException("value must be a texture object"); - } - this.value = value; - this.texture = (Texture) value; + setValue(value); } /** + * Gets the required color space for this texture parameter. * - * @return the color space required by this texture param + * @return the required color space ({@link ColorSpace}) */ public ColorSpace getColorSpace() { return colorSpace; } /** - * Set to {@link ColorSpace#Linear} if the texture color space has to be forced to linear - * instead of sRGB + * Set to {@link ColorSpace#Linear} if the texture color space has to be forced + * to linear instead of sRGB. + * * @param colorSpace the desired color space - * @see ColorSpace */ public void setColorSpace(ColorSpace colorSpace) { this.colorSpace = colorSpace; @@ -94,17 +110,17 @@ public void setColorSpace(ColorSpace colorSpace) { public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(0, "texture_unit", -1); - oc.write(texture, "texture", null); // For backwards compatibility - oc.write(colorSpace, "colorSpace", null); + // For backwards compatibility + oc.write(0, "texture_unit", -1); + oc.write((Texture) value, "texture", null); } @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - texture = (Texture) value; colorSpace = ic.readEnum("colorSpace", ColorSpace.class, null); } -} \ No newline at end of file + +} From 2c877fe6f35f33243d8b7888b78de1bbeca75eb7 Mon Sep 17 00:00:00 2001 From: richardTingle <6330028+richardTingle@users.noreply.github.com> Date: Sat, 1 Jun 2024 19:47:48 +0100 Subject: [PATCH 21/57] #2275 Improve assertions for invalid transforms. (#2276) * #2275 Improve assertions for invalid transforms. These are already caught by assertions in GLRenderer, but by that time it is unclear what call pushed in the bad values * #2275 Swap to use more generally available validation methods * #2275 Add license and javadoc * #2275 Correct copyright year * #2275 It is a quaternion --- .../main/java/com/jme3/math/Quaternion.java | 23 ++++++++ .../main/java/com/jme3/math/Transform.java | 6 ++ .../java/com/jme3/math/QuaternionTest.java | 59 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/math/QuaternionTest.java diff --git a/jme3-core/src/main/java/com/jme3/math/Quaternion.java b/jme3-core/src/main/java/com/jme3/math/Quaternion.java index 621d4799a7..3da3feb42f 100644 --- a/jme3-core/src/main/java/com/jme3/math/Quaternion.java +++ b/jme3-core/src/main/java/com/jme3/math/Quaternion.java @@ -1608,4 +1608,27 @@ public Quaternion clone() { throw new AssertionError(); // can not happen } } + + /** + * Tests whether the argument is a valid quaternion, returning false if it's + * null or if any component is NaN or infinite. + * + * @param quaternion the quaternion to test (unaffected) + * @return true if non-null and finite, otherwise false + */ + public static boolean isValidQuaternion(Quaternion quaternion) { + if (quaternion == null) { + return false; + } + if (Float.isNaN(quaternion.x) + || Float.isNaN(quaternion.y) + || Float.isNaN(quaternion.z) + || Float.isNaN(quaternion.w)) { + return false; + } + return !Float.isInfinite(quaternion.x) + && !Float.isInfinite(quaternion.y) + && !Float.isInfinite(quaternion.z) + && !Float.isInfinite(quaternion.w); + } } 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 bfcec39fdf..de5bfe8ce8 100644 --- a/jme3-core/src/main/java/com/jme3/math/Transform.java +++ b/jme3-core/src/main/java/com/jme3/math/Transform.java @@ -121,6 +121,7 @@ public Transform() { * @return the (modified) current instance (for chaining) */ public Transform setRotation(Quaternion rot) { + assert Quaternion.isValidQuaternion(rot) : "Invalid rotation " + rot; this.rot.set(rot); return this; } @@ -132,6 +133,7 @@ public Transform setRotation(Quaternion rot) { * @return the (modified) current instance (for chaining) */ public Transform setTranslation(Vector3f trans) { + assert Vector3f.isValidVector(trans) : "Invalid translation " + trans; this.translation.set(trans); return this; } @@ -152,6 +154,7 @@ public Vector3f getTranslation() { * @return the (modified) current instance (for chaining) */ public Transform setScale(Vector3f scale) { + assert Vector3f.isValidVector(scale) : "Invalid scale " + scale; this.scale.set(scale); return this; } @@ -163,6 +166,7 @@ public Transform setScale(Vector3f scale) { * @return the (modified) current instance (for chaining) */ public Transform setScale(float scale) { + assert Float.isFinite(scale) : "Invalid scale " + scale; this.scale.set(scale, scale, scale); return this; } @@ -286,6 +290,7 @@ public Transform combineWithParent(Transform parent) { * @return the (modified) current instance (for chaining) */ public Transform setTranslation(float x, float y, float z) { + assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid translation " + x + ", " + y + ", " + z; translation.set(x, y, z); return this; } @@ -299,6 +304,7 @@ public Transform setTranslation(float x, float y, float z) { * @return the (modified) current instance (for chaining) */ public Transform setScale(float x, float y, float z) { + assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid scale " + x + ", " + y + ", " + z; scale.set(x, y, z); return this; } diff --git a/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java b/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java new file mode 100644 index 0000000000..a81074de70 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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.math; + +import junit.framework.TestCase; + +/** + * Verifies that the {@link Quaternion} class works correctly. + * + * @author Richard Tingle (aka Richtea) + */ +public class QuaternionTest extends TestCase{ + + /** + * Verify that the {@link Quaternion#isValidQuaternion(com.jme3.math.Quaternion)} method works correctly. Testing + * for NaNs and infinities (which are not "valid") + */ + public void testIsValidQuaternion(){ + assertFalse(Quaternion.isValidQuaternion(new Quaternion(Float.NaN, 2.1f, 3.0f, 1.5f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(1f, Float.NaN, 3.0f, 1.5f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(1f, 2.1f, Float.NaN, 1.5f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(1f, 2.1f, 3.0f, Float.NaN))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(Float.POSITIVE_INFINITY, 1.5f, 1.9f, 2.0f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(Float.NEGATIVE_INFINITY, 2.5f, 8.2f, 3.0f))); + assertFalse(Quaternion.isValidQuaternion(null)); + + assertTrue(Quaternion.isValidQuaternion(new Quaternion())); + assertTrue(Quaternion.isValidQuaternion(new Quaternion(1.5f, -5.7f, 8.2f, 3.0f))); + } +} \ No newline at end of file From a6809b8ff39e9f0f682b409e0ecdfc9af01d4414 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Sun, 2 Jun 2024 15:35:02 -0400 Subject: [PATCH 22/57] Fix Casting error that occurred on certain GPUs (#2274) Fix Casting error that occurred on certain GPUs --- .../resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag | 2 +- .../src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag index c3e43e5afc..5a8d28d810 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag @@ -259,7 +259,7 @@ void main(){ alphaBlend = alphaBlend_2; } - texChannelForAlphaBlending = int(mod($i, 4.0)); //pick the correct channel (r g b or a) based on the layer's index + texChannelForAlphaBlending = int(mod(float($i), 4.0)); //pick the correct channel (r g b or a) based on the layer's index switch(texChannelForAlphaBlending) { case 0: finalAlphaBlendForLayer = alphaBlend.r; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag index a978b9804e..b3705383c3 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag @@ -246,7 +246,7 @@ void main(){ alphaBlend = alphaBlend_2; } - texChannelForAlphaBlending = int(mod($i, 4.0)); //pick the correct channel (r g b or a) based on the layer's index + texChannelForAlphaBlending = int(mod(float($i), 4.0)); //pick the correct channel (r g b or a) based on the layer's index switch(texChannelForAlphaBlending) { case 0: finalAlphaBlendForLayer = alphaBlend.r; From 79fd4fda7040d79054783ba8ac478c09f6d7787d Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sun, 9 Jun 2024 13:47:16 -0700 Subject: [PATCH 23/57] implement equals(), hashCode(), and isSimilar() for bounding volumes (#2252) * implement equals() and hashCode() for BoundingBox * implement equals() and hashCode() for BoundingSphere * implement isSimilar() for BoundingBox * implement isSimilar() for BoundingSphere * BoundingSphere: add comments to emphasize the treatment of checkPlane * TestBoundingSphere: test equals() and isSimilar() * jme3-core tests: add the TestBoundingBox class * BoundingVolume: add equals() and hashCode() methods * utilize super.equals() and super.hashCode() * refactor to utilize Objects.hash() --- .../java/com/jme3/bounding/BoundingBox.java | 73 +++++++++++++- .../com/jme3/bounding/BoundingSphere.java | 65 +++++++++++- .../com/jme3/bounding/BoundingVolume.java | 45 ++++++++- .../com/jme3/bounding/TestBoundingBox.java | 99 +++++++++++++++++++ .../com/jme3/bounding/TestBoundingSphere.java | 51 ++++++++++ 5 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java index b99d4d7351..6b0e023b34 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ import java.io.IOException; import java.nio.FloatBuffer; //import com.jme.scene.TriMesh; +import java.util.Objects; /** * BoundingBox describes a bounding volume as an axis-aligned box. @@ -587,6 +588,76 @@ public BoundingVolume clone(BoundingVolume store) { return rVal; } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingBox)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingBox otherBoundingBox = (BoundingBox) other; + if (Float.compare(xExtent, otherBoundingBox.xExtent) != 0) { + return false; + } else if (Float.compare(yExtent, otherBoundingBox.yExtent) != 0) { + return false; + } else if (Float.compare(zExtent, otherBoundingBox.zExtent) != 0) { + return false; + } else { + return super.equals(otherBoundingBox); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(xExtent, yExtent, zExtent); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding box, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param aabb the bounding box to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingBox aabb, float epsilon) { + if (aabb == null) { + return false; + } else if (Float.compare(Math.abs(aabb.xExtent - xExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.yExtent - yExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.zExtent - zExtent), epsilon) > 0) { + return false; + } else if (!center.isSimilar(aabb.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. * The form is: "[Center: vector xExtent: X.XX yExtent: Y.YY zExtent: 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 5b46846a23..67cf7263f8 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -651,6 +652,68 @@ public BoundingVolume clone(BoundingVolume store) { return new BoundingSphere(radius, center.clone()); } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingSphere)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingSphere otherBoundingSphere = (BoundingSphere) other; + if (Float.compare(radius, otherBoundingSphere.getRadius()) != 0) { + return false; + } else { + return super.equals(otherBoundingSphere); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(radius); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding sphere, using + * the specified tolerance. If {@code other} is null, false is returned. + * Either way, the current instance is unaffected. + * + * @param sphere the bounding sphere to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingSphere sphere, float epsilon) { + if (sphere == null) { + return false; + } else if (Float.compare(Math.abs(sphere.getRadius() - radius), epsilon) > 0) { + return false; + } else if (!center.isSimilar(sphere.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. * The form is: "Radius: RRR.SSSS Center: vector". diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java index 88cd4148ef..3a80764910 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; /** * BoundingVolume defines an interface for dealing with @@ -180,6 +181,48 @@ public final BoundingVolume transform(Transform trans) { */ public abstract BoundingVolume clone(BoundingVolume store); + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingVolume)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingVolume otherBoundingVolume = (BoundingVolume) other; + if (!center.equals(otherBoundingVolume.getCenter())) { + return false; + } + // The checkPlane field is ignored. + + return true; + } + + /** + * Returns a hash code. If two bounding volumes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(center); + // The checkPlane field is ignored. + + return hash; + } + public final Vector3f getCenter() { return center; } diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java new file mode 100644 index 0000000000..7a48e553b6 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 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.bounding; + +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for the BoundingBox class. + * + * @author Stephen Gold + */ +public class TestBoundingBox { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingBox bb1 = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1f, 2f); + BoundingBox bb2 + = new BoundingBox(new Vector3f(3f, 4f, 5f), -0f, 1f, 2f); + + BoundingBox bb3 = new BoundingBox(new Vector3f(3f, 0f, 2f), 9f, 8f, 7f); + BoundingBox bb4 + = new BoundingBox(new Vector3f(3f, -0f, 2f), 9f, 8f, 7f); + + BoundingBox bb5 = new BoundingBox(new Vector3f(4f, 5f, 6f), 9f, 8f, 7f); + BoundingBox bb6 = (BoundingBox) bb5.clone(); + bb6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bb1, bb1.clone()); + Assert.assertEquals(bb2, bb2.clone()); + Assert.assertEquals(bb3, bb3.clone()); + Assert.assertEquals(bb4, bb4.clone()); + Assert.assertEquals(bb5, bb5.clone()); + Assert.assertEquals(bb6, bb6.clone()); + + Assert.assertNotEquals(bb1, bb2); // because their extents differ + Assert.assertNotEquals(bb3, bb4); // because their centers differ + Assert.assertEquals(bb5, bb6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingBox bb1 = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1f, 2f); + BoundingBox bb2 + = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1.1f, 2f); + + BoundingBox bb3 = new BoundingBox(new Vector3f(3f, 4f, 2f), 9f, 8f, 7f); + BoundingBox bb4 + = new BoundingBox(new Vector3f(3f, 3.9f, 2f), 9f, 8f, 7f); + + BoundingBox bb5 = new BoundingBox(new Vector3f(4f, 5f, 6f), 9f, 8f, 7f); + BoundingBox bb6 = (BoundingBox) bb5.clone(); + bb6.setCheckPlane(1); + + Assert.assertFalse(bb1.isSimilar(bb2, 0.09999f)); + Assert.assertTrue(bb1.isSimilar(bb2, 0.10001f)); + + Assert.assertFalse(bb3.isSimilar(bb4, 0.09999f)); + Assert.assertTrue(bb3.isSimilar(bb4, 0.10001f)); + + Assert.assertTrue(bb5.isSimilar(bb6, 0f)); // check planes are ignored + } +} diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java index 7f75ec6550..4c0e760bc2 100644 --- a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java @@ -41,6 +41,57 @@ * @author Stephen Gold */ public class TestBoundingSphere { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(-0f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 0f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, -0f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bs1, bs1.clone()); + Assert.assertEquals(bs2, bs2.clone()); + Assert.assertEquals(bs3, bs3.clone()); + Assert.assertEquals(bs4, bs4.clone()); + Assert.assertEquals(bs5, bs5.clone()); + Assert.assertEquals(bs6, bs6.clone()); + + Assert.assertNotEquals(bs1, bs2); // because their radii differ + Assert.assertNotEquals(bs3, bs4); // because their centers differ + Assert.assertEquals(bs5, bs6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(0.1f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 4f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, 3.9f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + Assert.assertFalse(bs1.isSimilar(bs2, 0.09999f)); + Assert.assertTrue(bs1.isSimilar(bs2, 0.10001f)); + + Assert.assertFalse(bs3.isSimilar(bs4, 0.09999f)); + Assert.assertTrue(bs3.isSimilar(bs4, 0.10001f)); + + Assert.assertTrue(bs5.isSimilar(bs6, 0f)); // check planes are ignored + } /** * Verify that an infinite bounding sphere can be merged with a very From 69f50307d8270c4adc083964deda8afb6fb7ffbe Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 11 Jun 2024 08:45:28 -0700 Subject: [PATCH 24/57] June 2024 update to README.md (#2281) * README.md: add the "Star Colony: Beyond Horizons" to the game list * README.md: update web links to use HTTPS where possible --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f7670e888e..885d2d1d29 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ The engine is used by several commercial game studios and computer-science cours ![jME3 Games Mashup](https://i.imgur.com/nF8WOW6.jpg) - - [jME powered games on IndieDB](http://www.indiedb.com/engines/jmonkeyengine/games) + - [jME powered games on IndieDB](https://www.indiedb.com/engines/jmonkeyengine/games) - [Boardtastic 2](https://boardtastic-2.fileplanet.com/apk) - [Attack of the Gelatinous Blob](https://attack-gelatinous-blob.softwareandgames.com/) - - [Mythruna](http://mythruna.com/) + - [Mythruna](https://mythruna.com/) - [PirateHell (on Steam)](https://store.steampowered.com/app/321080/Pirate_Hell/) - - [3089 (on Steam)](http://store.steampowered.com/app/263360/) - - [3079 (on Steam)](http://store.steampowered.com/app/259620/) + - [3089 (on Steam)](https://store.steampowered.com/app/263360/3089__Futuristic_Action_RPG/) + - [3079 (on Steam)](https://store.steampowered.com/app/259620/3079__Block_Action_RPG/) - [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/) - [Skullstone](http://www.skullstonegame.com/) - [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/) @@ -33,12 +33,13 @@ The engine is used by several commercial game studios and computer-science cours - [Depthris (on Itch)](https://codewalker.itch.io/depthris) - [Stranded (on Itch)](https://tgiant.itch.io/stranded) - [The Afflicted Forests (Coming Soon to Steam)](https://www.indiedb.com/games/the-afflicted-forests) + - [Star Colony: Beyond Horizons (on Google Play)](https://play.google.com/store/apps/details?id=game.colony.ColonyBuilder) ## Getting Started Go to https://github.com/jMonkeyEngine/sdk/releases to download the jMonkeyEngine SDK. Read [the wiki](https://jmonkeyengine.github.io/wiki) for the installation guide and tutorials. -Join [the discussion forum](http://hub.jmonkeyengine.org/) to participate in our community, +Join [the discussion forum](https://hub.jmonkeyengine.org/) to participate in our community, get your questions answered, and share your projects. Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION. From b7787a7f78add2558bf650be7dbaf6754906cc90 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 11 Jun 2024 08:47:10 -0700 Subject: [PATCH 25/57] partial revert of PR #2060 (JSON parser abstraction) (#2278) --- jme3-examples/build.gradle | 4 ++-- jme3-plugins/build.gradle | 3 +-- .../plugins/gltf/CustomContentManager.java | 7 +++--- .../scene/plugins/gltf/ExtensionLoader.java | 5 ++-- .../jme3/scene/plugins/gltf/ExtrasLoader.java | 3 ++- .../jme3/scene/plugins/gltf/GltfLoader.java | 14 ++++++----- .../jme3/scene/plugins/gltf/GltfUtils.java | 23 ++++--------------- .../gltf/LightsPunctualExtensionLoader.java | 8 +++---- .../PBREmissiveStrengthExtensionLoader.java | 4 ++-- .../gltf/PBRSpecGlossExtensionLoader.java | 4 ++-- .../gltf/TextureTransformExtensionLoader.java | 8 +++---- .../plugins/gltf/UnlitExtensionLoader.java | 5 ++-- .../scene/plugins/gltf/UserDataLoader.java | 10 ++++---- 13 files changed, 43 insertions(+), 55 deletions(-) diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index b5979feceb..c51b2ae522 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -24,8 +24,8 @@ dependencies { implementation project(':jme3-networking') implementation project(':jme3-niftygui') implementation project(':jme3-plugins') - implementation project(':jme3-plugins-json') - implementation project(':jme3-plugins-json-gson') +// implementation project(':jme3-plugins-json') +// implementation project(':jme3-plugins-json-gson') implementation project(':jme3-terrain') implementation project(':jme3-awt-dialogs') runtimeOnly project(':jme3-testdata') diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 1d325314ee..b88f0a64fd 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -11,7 +11,6 @@ sourceSets { dependencies { api project(':jme3-core') - implementation project(':jme3-plugins-json') - implementation project(':jme3-plugins-json-gson') + api 'com.google.code.gson:gson:2.9.1' testRuntimeOnly project(':jme3-desktop') } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java index c713a72b58..df612f1a0a 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,10 +31,9 @@ */ package com.jme3.scene.plugins.gltf; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.jme3.asset.AssetLoadException; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonElement; - import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java index 033b200378..c3bd71f394 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,7 @@ */ package com.jme3.scene.plugins.gltf; - -import com.jme3.plugins.json.JsonElement; +import com.google.gson.JsonElement; import java.io.IOException; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java index 3c4c9819dc..c470478db1 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java @@ -30,7 +30,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonElement; + +import com.google.gson.JsonElement; /** * Interface to handle a glTF extra. diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 46adacd386..e4e308355d 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,10 +31,12 @@ */ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonObject; -import com.jme3.plugins.json.JsonPrimitive; -import com.jme3.plugins.json.JsonElement; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; import com.jme3.anim.*; import com.jme3.asset.*; import com.jme3.material.Material; @@ -121,7 +123,7 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws defaultMat.setFloat("Roughness", 1f); } - docRoot = parse(stream); + docRoot = JsonParser.parseReader(new JsonReader(new InputStreamReader(stream))).getAsJsonObject(); JsonObject asset = docRoot.getAsJsonObject().get("asset").getAsJsonObject(); getAsString(asset, "generator"); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index e67ed37430..0af3321aab 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,15 +31,13 @@ */ package com.jme3.scene.plugins.gltf; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; import com.jme3.math.*; import com.jme3.scene.*; -import com.jme3.plugins.json.Json; -import com.jme3.plugins.json.JsonParser; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonObject; -import com.jme3.plugins.json.JsonElement; import com.jme3.texture.Texture; import com.jme3.util.*; import java.io.*; @@ -61,18 +59,7 @@ public class GltfUtils { private GltfUtils() { } - - /** - * Parse a json input stream and returns a {@link JsonObject} - * @param stream the stream to parse - * @return the JsonObject - */ - public static JsonObject parse(InputStream stream) { - JsonParser parser = Json.create(); - return parser.parse(stream); - } - - public static Mesh.Mode getMeshMode(Integer mode) { + public static Mesh.Mode getMeshMode(Integer mode) { if (mode == null) { return Mesh.Mode.Triangles; } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java index b3df7dbc88..26d664ed82 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,9 +31,9 @@ */ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonObject; -import com.jme3.plugins.json.JsonElement; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.jme3.asset.AssetLoadException; import com.jme3.light.DirectionalLight; import com.jme3.light.Light; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java index ae7cb45c2c..c08d132307 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 jMonkeyEngine + * Copyright (c) 2023-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ package com.jme3.scene.plugins.gltf; import com.jme3.asset.AssetKey; -import com.jme3.plugins.json.JsonElement; +import com.google.gson.JsonElement; import java.io.IOException; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java index 2ab3862db3..7eca101de3 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +34,7 @@ import com.jme3.asset.AssetKey; import java.io.IOException; -import com.jme3.plugins.json.JsonElement; +import com.google.gson.JsonElement; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsColor; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index ea643cea0c..ecbcd2519f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,9 +31,9 @@ */ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonObject; -import com.jme3.plugins.json.JsonElement; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.jme3.asset.AssetLoadException; import com.jme3.math.Matrix3f; import com.jme3.math.Vector3f; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java index cb45fcc533..620c7f7e45 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,7 +30,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonElement; + +import com.google.gson.JsonElement; import com.jme3.asset.AssetKey; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java index df62f51685..5045f71b1b 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,10 +32,10 @@ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonElement; -import com.jme3.plugins.json.JsonObject; -import com.jme3.plugins.json.JsonPrimitive; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.jme3.scene.Spatial; import java.lang.reflect.Array; From f331e79c9630449eda095909080d53306e03ac53 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 14 Jun 2024 16:18:20 -0700 Subject: [PATCH 26/57] test and fix for issue #2282 (VerifyError while creating AXIS_SWEEP_3 space) (#2283) * update the "jbullet" library to v1.0.3 to address issue #2282 * add a simple test for issue #2282 --- .../java/jme3test/bullet/TestIssue2282.java | 68 +++++++++++++++++++ jme3-jbullet/build.gradle | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java new file mode 100644 index 0000000000..a9da19b01a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 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.PhysicsSpace; + +/** + * Test case for JME issue #2282: VerifyError while creating + * PhysicsSpace with AXIS_SWEEP_3 broadphase acceleration. + * + *

If successful, the application will print "SUCCESS" and terminate without + * crashing. If unsuccessful, the application will terminate with a VerifyError + * and no stack trace. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestIssue2282 extends SimpleApplication { + + /** + * Main entry point for the TestIssue2282 application. + * + * @param args array of command-line arguments (unused) + */ + public static void main(String[] args) { + TestIssue2282 test = new TestIssue2282(); + test.start(); + } + + /** + * Initialize the TestIssue2282 application. + */ + @Override + public void simpleInitApp() { + new PhysicsSpace(PhysicsSpace.BroadphaseType.AXIS_SWEEP_3); + System.out.println("SUCCESS"); + stop(); + } +} diff --git a/jme3-jbullet/build.gradle b/jme3-jbullet/build.gradle index 1a2621a420..0258bb4d54 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -14,7 +14,7 @@ sourceSets { } dependencies { - api 'com.github.stephengold:jbullet:1.0.2' + api 'com.github.stephengold:jbullet:1.0.3' api 'javax.vecmath:vecmath:1.5.2' api project(':jme3-core') api project(':jme3-terrain') From 88adea838aec0c4d7b59b9761ed336d5e5c7da0e Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 21 Jun 2024 09:29:32 -0700 Subject: [PATCH 27/57] TestBrickTower: delete secondary version of the BSD license (#2286) --- .../java/jme3test/bullet/TestBrickTower.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java index a265bdba96..a607878185 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java @@ -31,38 +31,6 @@ */ package jme3test.bullet; -/* - * 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. - */ - import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; import com.jme3.bullet.BulletAppState; From 6c0ab69adf152c9dc72adb91daec41edbe862bef Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Tue, 2 Jul 2024 14:37:37 -0400 Subject: [PATCH 28/57] Add MipMaps to PBRTerrainAdvancedTest.java (#2289) This test case has some noisey rendering that are due to not setting mipMaps on the TextureArray. So I added the MinFilter.Trilinear to fix this issue, and also added MagFilter.Bilinear. --- .../jme3test/terrain/PBRTerrainAdvancedTest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java index d36a55e09c..6127c279c0 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java @@ -52,6 +52,8 @@ import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.TextureArray; import java.util.ArrayList; import java.util.List; @@ -279,10 +281,10 @@ private void setUpTerrainMaterial() { TextureArray metallicRoughnessAoEiTextureArray = new TextureArray(metallicRoughnessAoEiMapImages); //apply wrapMode to the whole texture array, rather than each individual texture in the array - albedoTextureArray.setWrap(WrapMode.Repeat); - normalParallaxTextureArray.setWrap(WrapMode.Repeat); - metallicRoughnessAoEiTextureArray.setWrap(WrapMode.Repeat); - + setWrapAndMipMaps(albedoTextureArray); + setWrapAndMipMaps(normalParallaxTextureArray); + setWrapAndMipMaps(metallicRoughnessAoEiTextureArray); + //assign texture array to materials matTerrain.setParam("AlbedoTextureArray", VarType.TextureArray, albedoTextureArray); matTerrain.setParam("NormalParallaxTextureArray", VarType.TextureArray, normalParallaxTextureArray); @@ -430,6 +432,12 @@ private void setUpTerrain() { rootNode.attachChild(terrain); } + private void setWrapAndMipMaps(Texture texture){ + texture.setWrap(WrapMode.Repeat); + texture.setMinFilter(MinFilter.Trilinear); + texture.setMagFilter(MagFilter.Bilinear); + } + private void setUpLights() { LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); From 104e19e3088e842c83a8bb5207e8a14cc75a978c Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 4 Jul 2024 00:10:59 +0300 Subject: [PATCH 29/57] Setting the resolution to match the old prevents calling reshape (#2290) --- .../src/main/java/com/jme3/system/lwjgl/LwjglWindow.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index 4d92383b50..24a02bcd13 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -298,8 +298,6 @@ public void invoke(int error, long description) { requestWidth = videoMode.width(); requestHeight = videoMode.height(); } - oldFramebufferHeight = requestHeight; - oldFramebufferWidth = requestWidth; window = glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), monitor, NULL); if (window == NULL) { throw new RuntimeException("Failed to create the GLFW window"); From a3a9011ab16e430fac2567248528750b901a95fc Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Sun, 23 Jun 2024 14:03:36 -0400 Subject: [PATCH 30/57] Update gradle wrapper to 8.8 Fix #2287 --- build.gradle | 12 ++++---- common.gradle | 8 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 61624 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 31 ++++++++++++--------- gradlew.bat | 20 ++++++------- jme3-android-native/bufferallocator.gradle | 3 ++ jme3-android-native/decode.gradle | 3 ++ jme3-android-native/openalsoft.gradle | 3 ++ 9 files changed, 49 insertions(+), 34 deletions(-) diff --git a/build.gradle b/build.gradle index bb86d0101a..7deed462d8 100644 --- a/build.gradle +++ b/build.gradle @@ -74,23 +74,23 @@ task libDist(dependsOn: subprojects.build, description: 'Builds and copies the e subprojects.each {project -> if(!project.hasProperty('mainClassName')){ project.tasks.withType(Jar).each {archiveTask -> - if(archiveTask.classifier == "sources"){ + if(archiveTask.archiveClassifier == "sources"){ copy { from archiveTask.archivePath into sourceFolder - rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension} + rename {project.name + '-' + archiveTask.archiveClassifier +'.'+ archiveTask.archiveExtension} } - } else if(archiveTask.classifier == "javadoc"){ + } else if(archiveTask.archiveClassifier == "javadoc"){ copy { from archiveTask.archivePath into javadocFolder - rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension} + rename {project.name + '-' + archiveTask.archiveClassifier +'.'+ archiveTask.archiveExtension} } } else{ copy { from archiveTask.archivePath into libFolder - rename {project.name + '.' + archiveTask.extension} + rename {project.name + '.' + archiveTask.archiveExtension} } } } @@ -114,7 +114,7 @@ task createZipDistribution(type:Zip,dependsOn:["dist","libDist"], description:"P task copyLibs(type: Copy){ // description 'Copies the engine dependencies to build/libDist' from { - subprojects*.configurations*.compile*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() + subprojects*.configurations*.implementation*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() } into "$buildDir/libDist/lib-ext" //buildDir.path + '/' + libsDirName + '/lib' diff --git a/common.gradle b/common.gradle index a8aaa69230..90beb8427b 100644 --- a/common.gradle +++ b/common.gradle @@ -85,12 +85,12 @@ test { } task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets*.allSource } task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from the javadoc files.') { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -218,8 +218,8 @@ checkstyleTest { tasks.withType(Checkstyle) { reports { - xml.enabled false - html.enabled true + xml.required.set(false) + html.required.set(true) } include("**/com/jme3/renderer/**/*.java") } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index afba109285af78dbd2a1d187e33ac4f87c76e392..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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

NnX)EH+Nua)3Y(c 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�Zy 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c7d437bbb4..a4413138c9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d65..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/jme3-android-native/bufferallocator.gradle b/jme3-android-native/bufferallocator.gradle index f03027e275..d10335a7f2 100644 --- a/jme3-android-native/bufferallocator.gradle +++ b/jme3-android-native/bufferallocator.gradle @@ -44,6 +44,9 @@ task copyPreCompiledLibsBufferAllocator(type: Copy) { from file(bufferAllocatorPreCompiledLibsDir) into file(bufferAllocatorBuildLibsDir) } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledLibsBufferAllocator.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" diff --git a/jme3-android-native/decode.gradle b/jme3-android-native/decode.gradle index 27dc02dd5b..d6a3842f02 100644 --- a/jme3-android-native/decode.gradle +++ b/jme3-android-native/decode.gradle @@ -83,6 +83,9 @@ task copyPreCompiledLibs(type: Copy) { from sourceDir into outputDir } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledLibs.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" diff --git a/jme3-android-native/openalsoft.gradle b/jme3-android-native/openalsoft.gradle index 8c224ec457..3cff49b073 100644 --- a/jme3-android-native/openalsoft.gradle +++ b/jme3-android-native/openalsoft.gradle @@ -111,6 +111,9 @@ task copyPreCompiledOpenAlSoftLibs(type: Copy) { from sourceDir into outputDir } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledOpenAlSoftLibs.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" From 39a0ebbef4606abbc3f4f8e49bcf579097551fe1 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Sat, 6 Jul 2024 16:35:02 +0000 Subject: [PATCH 31/57] [skip ci] update natives snapshot --- natives-snapshot.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/natives-snapshot.properties b/natives-snapshot.properties index 2e7a470a18..50c937906c 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=3d49531fb98c8e41bf39d6a2167d5c3b95557148 +natives.snapshot=a3a9011ab16e430fac2567248528750b901a95fc From 5248fb0c1372eb00df9a666ab7881176eb4cdcb0 Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Sat, 6 Jul 2024 20:00:14 -0700 Subject: [PATCH 32/57] Use windows-latest image for windows CI (#2293) --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0cc3203965..c259226d7a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -90,13 +90,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest,windows-2019,macOS-latest] + os: [ubuntu-latest,windows-latest,macOS-latest] jdk: [11, 17] include: - os: ubuntu-latest osName: linux deploy: true - - os: windows-2019 + - os: windows-latest osName: windows deploy: false - os: macOS-latest From d99638ef38a85265115e09e19ea7dc9f25d1b48d Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Sun, 7 Jul 2024 11:29:19 -0700 Subject: [PATCH 33/57] Add java 21 to CI, start deploying with java 21 (#2292) Co-authored-by: Stephen Gold --- .github/workflows/main.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c259226d7a..42b566d944 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,7 +82,7 @@ jobs: name: android-natives path: build/native - # Build the engine, we only deploy from ubuntu-latest jdk17 + # Build the engine, we only deploy from ubuntu-latest jdk21 BuildJMonkey: needs: [BuildAndroidNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} @@ -91,7 +91,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest,windows-latest,macOS-latest] - jdk: [11, 17] + jdk: [11, 17, 21] include: - os: ubuntu-latest osName: linux @@ -104,6 +104,8 @@ jobs: deploy: false - jdk: 11 deploy: false + - jdk: 17 + deploy: false steps: - name: Clone the repo @@ -304,12 +306,12 @@ jobs: with: fetch-depth: 1 - # Setup jdk 17 used for building Maven-style artifacts + # Setup jdk 21 used for building Maven-style artifacts - name: Setup the java environment uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Download natives for android uses: actions/download-artifact@master @@ -348,12 +350,12 @@ jobs: with: fetch-depth: 1 - # Setup jdk 17 used for building Sonatype OSSRH artifacts + # Setup jdk 21 used for building Sonatype OSSRH artifacts - name: Setup the java environment uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' # Download all the stuff... - name: Download maven artifacts From 89000af21c0dabaad04815086c9b42e543e3a4dd Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Sat, 6 Jul 2024 10:52:09 -0700 Subject: [PATCH 34/57] Remove dead comments from gradle files --- build.gradle | 37 ------------------------------ jme3-android-examples/build.gradle | 1 - jme3-android-native/build.gradle | 3 --- 3 files changed, 41 deletions(-) diff --git a/build.gradle b/build.gradle index 7deed462d8..7b47851851 100644 --- a/build.gradle +++ b/build.gradle @@ -245,43 +245,6 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { } } - -//class IncrementalReverseTask extends DefaultTask { -// @InputDirectory -// def File inputDir -// -// @OutputDirectory -// def File outputDir -// -// @Input -// def inputProperty -// -// @TaskAction -// void execute(IncrementalTaskInputs inputs) { -// println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date" -// inputs.outOfDate { change -> -// println "out of date: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.text = change.file.text.reverse() -// } -// -// inputs.removed { change -> -// println "removed: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.delete() -// } -// } -//} - -//allprojects { -// tasks.withType(JavaExec) { -// enableAssertions = true // false by default -// } -// tasks.withType(Test) { -// enableAssertions = true // true by default -// } -//} - retrolambda { javaVersion JavaVersion.VERSION_1_7 incremental true diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle index 44bd254d84..9c2356ba7d 100644 --- a/jme3-android-examples/build.gradle +++ b/jme3-android-examples/build.gradle @@ -54,5 +54,4 @@ dependencies { implementation project(':jme3-plugins') implementation project(':jme3-terrain') implementation fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*']) -// compile project(':jme3-examples') } diff --git a/jme3-android-native/build.gradle b/jme3-android-native/build.gradle index cc78ab4722..5ec15daea1 100644 --- a/jme3-android-native/build.gradle +++ b/jme3-android-native/build.gradle @@ -28,11 +28,8 @@ ext { exclude ".gradle" }.asPath } -//println "projectClassPath = " + projectClassPath // add each native lib build file apply from: file('openalsoft.gradle') -// apply from: file('stb_image.gradle') -// apply from: file('tremor.gradle') apply from: file('decode.gradle') apply from: file('bufferallocator.gradle') From d633789c713983bde2ae3c1f171b7fedcb844212 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Sun, 7 Jul 2024 22:12:24 +0000 Subject: [PATCH 35/57] [skip ci] update natives snapshot --- natives-snapshot.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/natives-snapshot.properties b/natives-snapshot.properties index 50c937906c..c934eecfa7 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=a3a9011ab16e430fac2567248528750b901a95fc +natives.snapshot=89000af21c0dabaad04815086c9b42e543e3a4dd From 3ab36478ec59b7f8d6af271ca11ab26100df00ad Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Mon, 8 Jul 2024 07:42:40 -0700 Subject: [PATCH 36/57] Fix deprecated gradle features, prepare for gradle 9 (#2294) --- common.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common.gradle b/common.gradle index 90beb8427b..d332cba6d7 100644 --- a/common.gradle +++ b/common.gradle @@ -15,8 +15,10 @@ eclipse.jdt.file.withProperties { props -> group = 'org.jmonkeyengine' version = jmeFullVersion -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} tasks.withType(JavaCompile) { // compile-time options: //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings From cb200194367cb0237ff87a73df82e8cf09ac1d1e Mon Sep 17 00:00:00 2001 From: Night Rider <97632588+JNightRider@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:36:32 -0600 Subject: [PATCH 37/57] Cleaning at the code level (format) (#2299) Co-authored-by: wil --- .../jme3/anim/util/AnimMigrationUtils.java | 35 ++++++++++++- .../com/jme3/anim/util/HasLocalTransform.java | 31 +++++++++++ .../jme3/anim/util/JointModelTransform.java | 31 +++++++++++ .../java/com/jme3/anim/util/Primitives.java | 31 +++++++++++ .../java/com/jme3/anim/util/Weighted.java | 31 +++++++++++ .../com/jme3/app/state/CompositeAppState.java | 52 +++++++++---------- .../asset/cache/WeakRefCloneAssetCache.java | 3 +- .../jme3/audio/openal/ALAudioRenderer.java | 2 +- .../java/com/jme3/cinematic/Cinematic.java | 2 +- .../com/jme3/cinematic/events/AnimEvent.java | 2 +- .../jme3/environment/baker/IBLGLEnvBaker.java | 2 + .../com/jme3/export/SavableClassUtil.java | 2 +- .../com/jme3/font/BitmapCharacterSet.java | 2 +- .../main/java/com/jme3/font/ColorTags.java | 2 +- .../main/java/com/jme3/font/LetterQuad.java | 2 +- .../src/main/java/com/jme3/font/Letters.java | 4 +- .../java/com/jme3/input/AbstractJoystick.java | 12 ++--- .../main/java/com/jme3/input/CameraInput.java | 42 +++++++-------- .../main/java/com/jme3/input/ChaseCamera.java | 14 ++--- .../com/jme3/input/DefaultJoystickAxis.java | 14 ++--- .../com/jme3/input/DefaultJoystickButton.java | 10 ++-- .../main/java/com/jme3/input/FlyByCamera.java | 2 +- .../input/JoystickCompatibilityMappings.java | 4 +- .../com/jme3/input/event/JoyButtonEvent.java | 4 +- .../com/jme3/input/event/KeyInputEvent.java | 8 +-- .../jme3/input/event/MouseButtonEvent.java | 8 +-- .../jme3/input/event/MouseMotionEvent.java | 2 +- .../src/main/java/com/jme3/light/Light.java | 2 +- .../java/com/jme3/light/SphereProbeArea.java | 2 +- .../light/WeightedProbeBlendingStrategy.java | 2 +- .../main/java/com/jme3/material/Material.java | 2 +- .../SinglePassAndImageBasedLightingLogic.java | 3 +- .../src/main/java/com/jme3/math/Ring.java | 2 +- .../src/main/java/com/jme3/math/Vector3f.java | 16 +++--- .../src/main/java/com/jme3/math/Vector4f.java | 18 +++---- .../com/jme3/opencl/OpenCLObjectManager.java | 4 +- .../main/java/com/jme3/post/HDRRenderer.java | 2 +- .../java/com/jme3/post/PreDepthProcessor.java | 4 +- .../com/jme3/renderer/queue/GeometryList.java | 2 +- .../java/com/jme3/scene/SimpleBatchNode.java | 12 +++-- .../com/jme3/scene/control/AreaUtils.java | 3 -- .../main/java/com/jme3/scene/debug/Arrow.java | 4 +- .../jme3/scene/debug/SkeletonDebugger.java | 1 + .../debug/custom/ArmatureInterJointsWire.java | 2 +- .../jme3/scene/debug/custom/ArmatureNode.java | 18 +++---- .../jme3/scene/debug/custom/JointShape.java | 4 -- .../com/jme3/scene/mesh/IndexByteBuffer.java | 2 +- .../com/jme3/scene/mesh/IndexIntBuffer.java | 2 +- .../com/jme3/scene/mesh/IndexShortBuffer.java | 2 +- .../java/com/jme3/scene/mesh/MorphTarget.java | 2 +- .../jme3/shader/Glsl100ShaderGenerator.java | 2 +- .../jme3/shader/UniformBindingManager.java | 44 ++++++++-------- .../bufferobject/layout/BufferLayout.java | 7 +-- .../com/jme3/shadow/BasicShadowRenderer.java | 18 +++---- .../java/com/jme3/texture/FrameBuffer.java | 2 +- .../main/java/com/jme3/util/BufferUtils.java | 4 +- .../main/java/com/jme3/util/JmeFormatter.java | 12 ++--- .../src/main/java/com/jme3/util/ListMap.java | 2 +- .../com/jme3/util/NativeObjectManager.java | 4 +- .../java/com/jme3/util/SafeArrayList.java | 2 +- .../util/blockparser/BlockLanguageParser.java | 2 +- .../main/java/com/jme3/util/clone/Cloner.java | 4 +- .../com/jme3/util/functional/Function.java | 32 +++++++++++- .../jme3/util/functional/NoArgFunction.java | 32 +++++++++++- .../util/functional/NoArgVoidFunction.java | 32 +++++++++++- .../jme3/util/functional/VoidFunction.java | 32 +++++++++++- .../MikktspaceTangentGenerator.java | 10 ++-- .../struct/fields/SubStructArrayField.java | 2 + .../com/jme3/asset/plugins/UrlAssetInfo.java | 2 +- .../jme3/export/binary/BinaryExporter.java | 6 +-- .../jme3/export/binary/BinaryImporter.java | 8 +-- .../com/jme3/material/plugins/J3MLoader.java | 8 ++- .../com/jme3/shader/plugins/GLSLLoader.java | 2 +- .../java/jme3tools/optimize/LodGenerator.java | 4 +- .../java/jme3tools/optimize/TextureAtlas.java | 10 ++-- 75 files changed, 510 insertions(+), 238 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java index b8e40a2b4c..6c463f329d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 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.anim.util; import com.jme3.anim.*; @@ -10,8 +41,8 @@ public class AnimMigrationUtils { - final private static AnimControlVisitor animControlVisitor = new AnimControlVisitor(); - final private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); + private static final AnimControlVisitor animControlVisitor = new AnimControlVisitor(); + private static final SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); /** * A private constructor to inhibit instantiation of this class. diff --git a/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java index 28f560dfce..f483cce7f0 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 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.anim.util; import com.jme3.export.Savable; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java index 837c50ca2e..708d1410dd 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 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.anim.util; import com.jme3.anim.Joint; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java index 56fbbf4788..4e04c49cbb 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 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.anim.util; import java.util.Collections; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java index f771d44edd..1a2a690d1b 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 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.anim.util; import com.jme3.anim.tween.action.Action; diff --git a/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java b/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java index 953a5af4c1..c8d2d9acb5 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java @@ -63,41 +63,41 @@ public class CompositeAppState extends BaseAppState { private AppStateManager stateManager; private boolean attached; - public CompositeAppState( AppState... states ) { - for( AppState a : states ) { + public CompositeAppState(AppState... states) { + for (AppState a : states) { this.states.add(new AppStateEntry(a, false)); } } - private int indexOf( AppState state ) { - for( int i = 0; i < states.size(); i++ ) { + private int indexOf(AppState state) { + for (int i = 0; i < states.size(); i++) { AppStateEntry e = states.get(i); - if( e.state == state ) { + if (e.state == state) { return i; } } return -1; } - private AppStateEntry entry( AppState state ) { - for( AppStateEntry e : states.getArray() ) { - if( e.state == state ) { + private AppStateEntry entry(AppState state) { + for (AppStateEntry e : states.getArray()) { + if (e.state == state) { return e; } } return null; } - protected T addChild( T state ) { + protected T addChild(T state) { return addChild(state, false); } - protected T addChild( T state, boolean overrideEnable ) { - if( indexOf(state) >= 0 ) { + protected T addChild(T state, boolean overrideEnable) { + if (indexOf(state) >= 0) { return state; } states.add(new AppStateEntry(state, overrideEnable)); - if( attached ) { + if (attached) { stateManager.attach(state); } return state; @@ -130,30 +130,30 @@ protected void clearChildren() { } @Override - public void stateAttached( AppStateManager stateManager ) { + public void stateAttached(AppStateManager stateManager) { this.stateManager = stateManager; - for( AppStateEntry e : states.getArray() ) { + for (AppStateEntry e : states.getArray()) { stateManager.attach(e.state); } this.attached = true; } @Override - public void stateDetached( AppStateManager stateManager ) { + public void stateDetached(AppStateManager stateManager) { // Reverse order - for( int i = states.size() - 1; i >= 0; i-- ) { + for (int i = states.size() - 1; i >= 0; i--) { stateManager.detach(states.get(i).state); } this.attached = false; this.stateManager = null; } - protected void setChildrenEnabled( boolean b ) { - if( childrenEnabled == b ) { + protected void setChildrenEnabled(boolean b) { + if(childrenEnabled == b) { return; } childrenEnabled = b; - for( AppStateEntry e : states.getArray() ) { + for (AppStateEntry e : states.getArray()) { e.setEnabled(b); } } @@ -170,12 +170,12 @@ protected void setChildrenEnabled( boolean b ) { * too. Override is about remembering the child's state before that * happened and restoring it when the 'family' is enabled again as a whole. */ - public void setOverrideEnabled( AppState state, boolean override ) { + public void setOverrideEnabled(AppState state, boolean override) { AppStateEntry e = entry(state); - if( e == null ) { + if (e == null) { throw new IllegalArgumentException("State not managed:" + state); } - if( override ) { + if (override) { e.override = true; } else { e.override = false; @@ -206,16 +206,16 @@ private class AppStateEntry { boolean enabled; boolean override; - public AppStateEntry( AppState state, boolean overrideEnable ) { + public AppStateEntry(AppState state, boolean overrideEnable) { this.state = state; this.override = overrideEnable; this.enabled = state.isEnabled(); } - public void setEnabled( boolean b ) { + public void setEnabled(boolean b) { - if( override ) { - if( b ) { + if (override) { + if (b) { // Set it to whatever its enabled state // was before going disabled last time. state.setEnabled(enabled); 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 5e0a90c173..722afde411 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 @@ -60,8 +60,7 @@ public class WeakRefCloneAssetCache implements AssetCache { * Maps cloned key to AssetRef which has a weak ref to the original * key and a strong ref to the original asset. */ - private final ConcurrentHashMap smartCache - = new ConcurrentHashMap<>(); + private final ConcurrentHashMap smartCache = new ConcurrentHashMap<>(); /** * Stored in the ReferenceQueue to find out when originalKey is collected 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 834d80eca1..aa3c77bbf2 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 @@ -55,7 +55,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { // which is exactly 1 second of audio. private static final int BUFFER_SIZE = 35280; private static final int STREAMING_BUFFER_COUNT = 5; - private final static int MAX_NUM_CHANNELS = 64; + private static final int MAX_NUM_CHANNELS = 64; private IntBuffer ib = BufferUtils.createIntBuffer(1); private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); diff --git a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java index b03850c255..daca01b843 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java @@ -93,7 +93,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { private Node scene; protected TimeLine timeLine = new TimeLine(); private int lastFetchedKeyFrame = -1; - final private List cinematicEvents = new ArrayList<>(); + private final List cinematicEvents = new ArrayList<>(); private Map cameras = new HashMap<>(); private CameraNode currentCam; private boolean initialized = false; diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java index 34c5275d20..7d7721e166 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java @@ -53,7 +53,7 @@ */ public class AnimEvent extends AbstractCinematicEvent { - final public static Logger logger + public static final Logger logger = Logger.getLogger(AnimEvent.class.getName()); /* diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java index 337ab90322..5b79a4922f 100644 --- a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java @@ -109,10 +109,12 @@ public IBLGLEnvBaker(RenderManager rm, AssetManager am, Format format, Format de brtf.getImage().setColorSpace(ColorSpace.Linear); } + @Override public TextureCubeMap getSpecularIBL() { return specular; } + @Override public TextureCubeMap getIrradiance() { return irradiance; } diff --git a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java index 2c63050a4d..79364c96ea 100644 --- a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java +++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java @@ -57,7 +57,7 @@ */ public class SavableClassUtil { - private final static HashMap CLASS_REMAPPINGS = new HashMap<>(); + private static final HashMap CLASS_REMAPPINGS = new HashMap<>(); private static void addRemapping(String oldClass, Class newClass) { CLASS_REMAPPINGS.put(oldClass, newClass.getName()); diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java index 86e6a5fd8e..13b16785d7 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java @@ -43,7 +43,7 @@ public class BitmapCharacterSet implements Savable { private int renderedSize; private int width; private int height; - final private IntMap> characters; + private final IntMap> characters; private int pageSize; @Override diff --git a/jme3-core/src/main/java/com/jme3/font/ColorTags.java b/jme3-core/src/main/java/com/jme3/font/ColorTags.java index a64806a785..304b9832a6 100644 --- a/jme3-core/src/main/java/com/jme3/font/ColorTags.java +++ b/jme3-core/src/main/java/com/jme3/font/ColorTags.java @@ -47,7 +47,7 @@ class ColorTags { private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" + "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#"); - final private LinkedList colors = new LinkedList<>(); + private final LinkedList colors = new LinkedList<>(); private String text; private String original; private float baseAlpha = -1; diff --git a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java index 2611a059f2..ff647ab423 100644 --- a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java +++ b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java @@ -66,7 +66,7 @@ class LetterQuad { private LetterQuad next; private int colorInt = 0xFFFFFFFF; - final private boolean rightToLeft; + private final boolean rightToLeft; private float alignX; private float alignY; private float sizeScale = 1; diff --git a/jme3-core/src/main/java/com/jme3/font/Letters.java b/jme3-core/src/main/java/com/jme3/font/Letters.java index 0b9473d03e..b545dee035 100644 --- a/jme3-core/src/main/java/com/jme3/font/Letters.java +++ b/jme3-core/src/main/java/com/jme3/font/Letters.java @@ -47,10 +47,10 @@ class Letters { private final LetterQuad tail; private final BitmapFont font; private LetterQuad current; - final private StringBlock block; + private final StringBlock block; private float totalWidth; private float totalHeight; - final private ColorTags colorTags = new ColorTags(); + private final ColorTags colorTags = new ColorTags(); private ColorRGBA baseColor = null; private float baseAlpha = -1; private String plainText; diff --git a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java index 9381f510b7..7eab91ee12 100644 --- a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java +++ b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java @@ -42,13 +42,13 @@ */ public abstract class AbstractJoystick implements Joystick { - final private InputManager inputManager; - final private JoyInput joyInput; - final private int joyId; - final private String name; + private final InputManager inputManager; + private final JoyInput joyInput; + private final int joyId; + private final String name; - final private List axes = new ArrayList<>(); - final private List buttons = new ArrayList<>(); + private final List axes = new ArrayList<>(); + private final List buttons = new ArrayList<>(); /** * Creates a new joystick instance. Only used internally. diff --git a/jme3-core/src/main/java/com/jme3/input/CameraInput.java b/jme3-core/src/main/java/com/jme3/input/CameraInput.java index 1d40c716f4..b4e46c8ad2 100644 --- a/jme3-core/src/main/java/com/jme3/input/CameraInput.java +++ b/jme3-core/src/main/java/com/jme3/input/CameraInput.java @@ -44,37 +44,37 @@ public class CameraInput { * Chase camera mapping for moving down. Default assigned to * MouseInput.AXIS_Y direction depending on the invertYaxis configuration */ - public final static String CHASECAM_DOWN = "ChaseCamDown"; + public static final String CHASECAM_DOWN = "ChaseCamDown"; /** * Chase camera mapping for moving up. Default assigned to MouseInput.AXIS_Y * direction depending on the invertYaxis configuration */ - public final static String CHASECAM_UP = "ChaseCamUp"; + public static final String CHASECAM_UP = "ChaseCamUp"; /** * Chase camera mapping for zooming in. Default assigned to * MouseInput.AXIS_WHEEL direction positive */ - public final static String CHASECAM_ZOOMIN = "ChaseCamZoomIn"; + public static final String CHASECAM_ZOOMIN = "ChaseCamZoomIn"; /** * Chase camera mapping for zooming out. Default assigned to * MouseInput.AXIS_WHEEL direction negative */ - public final static String CHASECAM_ZOOMOUT = "ChaseCamZoomOut"; + public static final String CHASECAM_ZOOMOUT = "ChaseCamZoomOut"; /** * Chase camera mapping for moving left. Default assigned to * MouseInput.AXIS_X direction depending on the invertXaxis configuration */ - public final static String CHASECAM_MOVELEFT = "ChaseCamMoveLeft"; + public static final String CHASECAM_MOVELEFT = "ChaseCamMoveLeft"; /** * Chase camera mapping for moving right. Default assigned to * MouseInput.AXIS_X direction depending on the invertXaxis configuration */ - public final static String CHASECAM_MOVERIGHT = "ChaseCamMoveRight"; + public static final String CHASECAM_MOVERIGHT = "ChaseCamMoveRight"; /** * Chase camera mapping to initiate the rotation of the cam. Default assigned * to MouseInput.BUTTON_LEFT and MouseInput.BUTTON_RIGHT */ - public final static String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate"; + public static final String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate"; @@ -83,63 +83,63 @@ public class CameraInput { * Fly camera mapping to look left. Default assigned to MouseInput.AXIS_X, * direction negative */ - public final static String FLYCAM_LEFT = "FLYCAM_Left"; + public static final String FLYCAM_LEFT = "FLYCAM_Left"; /** * Fly camera mapping to look right. Default assigned to MouseInput.AXIS_X, * direction positive */ - public final static String FLYCAM_RIGHT = "FLYCAM_Right"; + public static final String FLYCAM_RIGHT = "FLYCAM_Right"; /** * Fly camera mapping to look up. Default assigned to MouseInput.AXIS_Y, * direction positive */ - public final static String FLYCAM_UP = "FLYCAM_Up"; + public static final String FLYCAM_UP = "FLYCAM_Up"; /** * Fly camera mapping to look down. Default assigned to MouseInput.AXIS_Y, * direction negative */ - public final static String FLYCAM_DOWN = "FLYCAM_Down"; + public static final String FLYCAM_DOWN = "FLYCAM_Down"; /** * Fly camera mapping to move left. Default assigned to KeyInput.KEY_A */ - public final static String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft"; + public static final String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft"; /** * Fly camera mapping to move right. Default assigned to KeyInput.KEY_D */ - public final static String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight"; + public static final String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight"; /** * Fly camera mapping to move forward. Default assigned to KeyInput.KEY_W */ - public final static String FLYCAM_FORWARD = "FLYCAM_Forward"; + public static final String FLYCAM_FORWARD = "FLYCAM_Forward"; /** * Fly camera mapping to move backward. Default assigned to KeyInput.KEY_S */ - public final static String FLYCAM_BACKWARD = "FLYCAM_Backward"; + public static final String FLYCAM_BACKWARD = "FLYCAM_Backward"; /** * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL, * direction positive */ - public final static String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn"; + public static final String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn"; /** * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL, * direction negative */ - public final static String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut"; + public static final String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut"; /** * Fly camera mapping to toggle rotation. Default assigned to * MouseInput.BUTTON_LEFT */ - public final static String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag"; + public static final String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag"; /** * Fly camera mapping to move up. Default assigned to KeyInput.KEY_Q */ - public final static String FLYCAM_RISE = "FLYCAM_Rise"; + public static final String FLYCAM_RISE = "FLYCAM_Rise"; /** * Fly camera mapping to move down. Default assigned to KeyInput.KEY_W */ - public final static String FLYCAM_LOWER = "FLYCAM_Lower"; + public static final String FLYCAM_LOWER = "FLYCAM_Lower"; - public final static String FLYCAM_INVERTY = "FLYCAM_InvertY"; + public static final String FLYCAM_INVERTY = "FLYCAM_InvertY"; /** * A private constructor to inhibit instantiation of this class. diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java index 057e729062..fe494801b3 100644 --- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -105,37 +105,37 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control, Jme * @deprecated use {@link CameraInput#CHASECAM_DOWN} */ @Deprecated - public final static String ChaseCamDown = "ChaseCamDown"; + public static final String ChaseCamDown = "ChaseCamDown"; /** * @deprecated use {@link CameraInput#CHASECAM_UP} */ @Deprecated - public final static String ChaseCamUp = "ChaseCamUp"; + public static final String ChaseCamUp = "ChaseCamUp"; /** * @deprecated use {@link CameraInput#CHASECAM_ZOOMIN} */ @Deprecated - public final static String ChaseCamZoomIn = "ChaseCamZoomIn"; + public static final String ChaseCamZoomIn = "ChaseCamZoomIn"; /** * @deprecated use {@link CameraInput#CHASECAM_ZOOMOUT} */ @Deprecated - public final static String ChaseCamZoomOut = "ChaseCamZoomOut"; + public static final String ChaseCamZoomOut = "ChaseCamZoomOut"; /** * @deprecated use {@link CameraInput#CHASECAM_MOVELEFT} */ @Deprecated - public final static String ChaseCamMoveLeft = "ChaseCamMoveLeft"; + public static final String ChaseCamMoveLeft = "ChaseCamMoveLeft"; /** * @deprecated use {@link CameraInput#CHASECAM_MOVERIGHT} */ @Deprecated - public final static String ChaseCamMoveRight = "ChaseCamMoveRight"; + public static final String ChaseCamMoveRight = "ChaseCamMoveRight"; /** * @deprecated use {@link CameraInput#CHASECAM_TOGGLEROTATE} */ @Deprecated - public final static String ChaseCamToggleRotate = "ChaseCamToggleRotate"; + public static final String ChaseCamToggleRotate = "ChaseCamToggleRotate"; protected boolean zoomin; protected boolean hideCursorOnRotate = true; 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 6e6bff046f..212f500e3b 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java @@ -40,13 +40,13 @@ */ public class DefaultJoystickAxis implements JoystickAxis { - final private InputManager inputManager; - final private Joystick parent; - final private int axisIndex; - final private String name; - final private String logicalId; - final private boolean isAnalog; - final private boolean isRelative; + private final InputManager inputManager; + private final Joystick parent; + private final int axisIndex; + private final String name; + private final String logicalId; + private final boolean isAnalog; + private final boolean isRelative; private float deadZone; /** diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java index 63cc1e9bfe..2dd45d4191 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java @@ -40,11 +40,11 @@ */ public class DefaultJoystickButton implements JoystickButton { - final private InputManager inputManager; - final private Joystick parent; - final private int buttonIndex; - final private String name; - final private String logicalId; + private final InputManager inputManager; + private final Joystick parent; + private final int buttonIndex; + private final String name; + private final String logicalId; public DefaultJoystickButton(InputManager inputManager, Joystick parent, int buttonIndex, String name, String logicalId) { diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java index 2f875bc98d..e3eb0da7e4 100644 --- a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java @@ -56,7 +56,7 @@ */ public class FlyByCamera implements AnalogListener, ActionListener { - final private static String[] mappings = new String[]{ + private static final String[] mappings = new String[]{ CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java index 3bc6187700..d2385be4f9 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java +++ b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java @@ -72,8 +72,8 @@ public class JoystickCompatibilityMappings { private static Map> buttonMappings = new HashMap>(); // Remaps names by regex. - final private static Map nameRemappings = new HashMap<>(); - final private static Map nameCache = new HashMap<>(); + private static final Map nameRemappings = new HashMap<>(); + private static final Map nameCache = new HashMap<>(); static { loadDefaultMappings(); diff --git a/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java index 5fca5d5703..615bb90212 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java @@ -41,8 +41,8 @@ */ public class JoyButtonEvent extends InputEvent { - final private JoystickButton button; - final private boolean pressed; + private final JoystickButton button; + private final boolean pressed; public JoyButtonEvent(JoystickButton button, boolean pressed) { this.button = button; diff --git a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java index f693d2caad..2fb0294730 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java @@ -40,10 +40,10 @@ */ public class KeyInputEvent extends InputEvent { - final private int keyCode; - final private char keyChar; - final private boolean pressed; - final private boolean repeating; + private final int keyCode; + private final char keyChar; + private final boolean pressed; + private final boolean repeating; public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) { this.keyCode = keyCode; diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java index 83c383cef7..0d5ddc0a52 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java @@ -40,10 +40,10 @@ */ public class MouseButtonEvent extends InputEvent { - final private int x; - final private int y; - final private int btnIndex; - final private boolean pressed; + private final int x; + private final int y; + private final int btnIndex; + private final boolean pressed; public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) { this.btnIndex = btnIndex; diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java index e88c09c1b6..0bf269f3cc 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java @@ -40,7 +40,7 @@ */ public class MouseMotionEvent extends InputEvent { - final private int x, y, dx, dy, wheel, deltaWheel; + private final int x, y, dx, dy, wheel, deltaWheel; public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) { this.x = x; diff --git a/jme3-core/src/main/java/com/jme3/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java index 4cbea59208..cebb4ae2a0 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -87,7 +87,7 @@ public enum Type { Probe(4); - final private int typeId; + private final int typeId; Type(int type){ this.typeId = type; diff --git a/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java index 89f7c07635..bf5bd1d539 100644 --- a/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java +++ b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java @@ -13,7 +13,7 @@ public class SphereProbeArea implements ProbeArea { private Vector3f center = new Vector3f(); private float radius = 1; - final private Matrix4f uniformMatrix = new Matrix4f(); + private final Matrix4f uniformMatrix = new Matrix4f(); public SphereProbeArea() { } diff --git a/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java index 0f3013cba6..5c5da45d8e 100644 --- a/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java +++ b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java @@ -45,7 +45,7 @@ */ public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy { - private final static int MAX_PROBES = 3; + private static final int MAX_PROBES = 3; List lightProbes = new ArrayList<>(); @Override 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 1352fa393b..86e8ecca5e 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -83,7 +83,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { private Technique technique; private HashMap techniques = new HashMap<>(); private RenderState additionalState = null; - final private RenderState mergedRenderState = new RenderState(); + private final RenderState mergedRenderState = new RenderState(); private boolean transparent = false; private boolean receivesShadows = false; private int sortingId = -1; diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java index 127586fcde..8e38f2e6ca 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java @@ -55,7 +55,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique private boolean useAmbientLight; private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); - final private List lightProbes = new ArrayList<>(3); + private final List lightProbes = new ArrayList<>(3); static { ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); @@ -278,7 +278,6 @@ public void render(RenderManager renderManager, Shader shader, Geometry geometry renderMeshFromGeometry(renderer, geometry); } } - return; } protected void extractIndirectLights(LightList lightList, boolean removeLights) { diff --git a/jme3-core/src/main/java/com/jme3/math/Ring.java b/jme3-core/src/main/java/com/jme3/math/Ring.java index c6d426d8a7..c1a17f3cf4 100644 --- a/jme3-core/src/main/java/com/jme3/math/Ring.java +++ b/jme3-core/src/main/java/com/jme3/math/Ring.java @@ -47,7 +47,7 @@ public final class Ring implements Savable, Cloneable, java.io.Serializable { private Vector3f center, up; private float innerRadius, outerRadius; - private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f(); + private static transient Vector3f b1 = new Vector3f(), b2 = new Vector3f(); /** * Constructor creates a new Ring lying on the XZ plane, diff --git a/jme3-core/src/main/java/com/jme3/math/Vector3f.java b/jme3-core/src/main/java/com/jme3/math/Vector3f.java index 89efe8a6fd..fa5a8539cd 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -52,32 +52,32 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable /** * Shared instance of the all-zero vector (0,0,0). Do not modify! */ - public final static Vector3f ZERO = new Vector3f(0, 0, 0); + public static final Vector3f ZERO = new Vector3f(0, 0, 0); /** * Shared instance of the all-NaN vector (NaN,NaN,NaN). Do not modify! */ - public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + public static final Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); /** * Shared instance of the +X direction (1,0,0). Do not modify! */ - public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); + public static final Vector3f UNIT_X = new Vector3f(1, 0, 0); /** * Shared instance of the +Y direction (0,1,0). Do not modify! */ - public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); + public static final Vector3f UNIT_Y = new Vector3f(0, 1, 0); /** * Shared instance of the +Z direction (0,0,1). Do not modify! */ - public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); + public static final Vector3f UNIT_Z = new Vector3f(0, 0, 1); /** * Shared instance of the all-ones vector (1,1,1). Do not modify! */ - public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + public static final Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); /** * Shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf). Do not * modify! */ - public final static Vector3f POSITIVE_INFINITY = new Vector3f( + public static final Vector3f POSITIVE_INFINITY = new Vector3f( Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); @@ -85,7 +85,7 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable * Shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf). Do * not modify! */ - public final static Vector3f NEGATIVE_INFINITY = new Vector3f( + public static final Vector3f NEGATIVE_INFINITY = new Vector3f( Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); diff --git a/jme3-core/src/main/java/com/jme3/math/Vector4f.java b/jme3-core/src/main/java/com/jme3/math/Vector4f.java index 826cf29030..cf18c4be50 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector4f.java @@ -51,36 +51,36 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable /** * shared instance of the all-zero vector (0,0,0,0) - Do not modify! */ - public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0); + public static final Vector4f ZERO = new Vector4f(0, 0, 0, 0); /** * shared instance of the all-NaN vector (NaN,NaN,NaN,NaN) - Do not modify! */ - public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); + public static final Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); /** * shared instance of the +X direction (1,0,0,0) - Do not modify! */ - public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); + public static final Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); /** * shared instance of the +Y direction (0,1,0,0) - Do not modify! */ - public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); + public static final Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); /** * shared instance of the +Z direction (0,0,1,0) - Do not modify! */ - public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); + public static final Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); /** * shared instance of the +W direction (0,0,0,1) - Do not modify! */ - public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); + public static final Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); /** * shared instance of the all-ones vector (1,1,1,1) - Do not modify! */ - public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); + public static final Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); /** * shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf,+Inf) * - Do not modify! */ - public final static Vector4f POSITIVE_INFINITY = new Vector4f( + public static final Vector4f POSITIVE_INFINITY = new Vector4f( Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, @@ -89,7 +89,7 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable * shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf,-Inf) * - Do not modify! */ - public final static Vector4f NEGATIVE_INFINITY = new Vector4f( + public static final Vector4f NEGATIVE_INFINITY = new Vector4f( Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java index 42748855cd..bb5d95b6dd 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java +++ b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java @@ -53,8 +53,8 @@ public static OpenCLObjectManager getInstance() { return INSTANCE; } - final private ReferenceQueue refQueue = new ReferenceQueue<>(); - final private HashSet activeObjects = new HashSet<>(); + private final ReferenceQueue refQueue = new ReferenceQueue<>(); + private final HashSet activeObjects = new HashSet<>(); private static class OpenCLObjectRef extends PhantomReference { diff --git a/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java index 2e31413d24..857e78dbc3 100644 --- a/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java +++ b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java @@ -95,7 +95,7 @@ public class HDRRenderer implements SceneProcessor { private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps; private MagFilter fbMagFilter = MagFilter.Bilinear; - final private AssetManager manager; + private final AssetManager manager; private boolean enabled = true; diff --git a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java index 6de0655fab..1a7a7be94f 100644 --- a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java @@ -49,8 +49,8 @@ public class PreDepthProcessor implements SceneProcessor { private RenderManager rm; private ViewPort vp; - final private Material preDepth; - final private RenderState forcedRS; + private final Material preDepth; + private final RenderState forcedRS; public PreDepthProcessor(AssetManager assetManager){ preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java index 5836c87614..546e766835 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -51,7 +51,7 @@ public class GeometryList implements Iterable{ private static final int DEFAULT_SIZE = 32; private Geometry[] geometries; - final private ListSort listSort; + private final ListSort listSort; private int size; private GeometryComparator comparator; diff --git a/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java index 2375d4ae37..426c636079 100644 --- a/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java @@ -39,7 +39,9 @@ * SimpleBatchNode comes with some restrictions, but can yield better performances. * Geometries to be batched has to be attached directly to the BatchNode * You can't attach a Node to a SimpleBatchNode - * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure. + * SimpleBatchNode is recommended when you have a large number of geometries using the + * same material that does not require a complex scene graph structure. + * * @see BatchNode * @author Nehon */ @@ -55,9 +57,9 @@ public SimpleBatchNode(String name) { @Override public int attachChild(Spatial child) { - if (!(child instanceof Geometry)) { - throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure"); + throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child " + + "of type Geometry, use BatchMode.Complex to use a complex structure"); } return super.attachChild(child); @@ -71,7 +73,8 @@ protected void setTransformRefresh() { batch.geometry.setTransformRefresh(); } } - final private Matrix4f cachedLocalMat = new Matrix4f(); + + private final Matrix4f cachedLocalMat = new Matrix4f(); @Override protected Matrix4f getTransformMatrix(Geometry g){ @@ -89,7 +92,6 @@ protected Matrix4f getTransformMatrix(Geometry g){ return cachedLocalMat; } - @Override public void batch() { doBatch(); diff --git a/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java index 52f388c7df..0097191ca8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java @@ -31,10 +31,7 @@ */ package com.jme3.scene.control; -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; -import com.jme3.math.FastMath; /** * AreaUtils is used to calculate the area of various objects, such as bounding volumes. These diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java index 05f01e4367..8f46298693 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java @@ -47,8 +47,8 @@ */ public class Arrow extends Mesh { - final private Quaternion tempQuat = new Quaternion(); - final private Vector3f tempVec = new Vector3f(); + private final Quaternion tempQuat = new Quaternion(); + private final Vector3f tempVec = new Vector3f(); private static final float[] positions = new float[]{ 0, 0, 0, diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java index 8068ae37f8..f8f450ab47 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java @@ -150,6 +150,7 @@ public void read(JmeImporter importer) throws IOException { interBoneWires = getMesh("_interwires"); } + @SuppressWarnings("unchecked") private T getMesh(String suffix) { Geometry child = (Geometry)getChild(getGeometryName(suffix)); if(child != null) { diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java index 612d6c9551..1fe8bdcdc6 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java @@ -46,7 +46,7 @@ * @author Marcin Roguski (Kaelthas) */ public class ArmatureInterJointsWire extends Mesh { - final private Vector3f tmp = new Vector3f(); + private final Vector3f tmp = new Vector3f(); public ArmatureInterJointsWire(Vector3f start, Vector3f[] ends) { diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java index 75acec75f8..c46466f181 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java @@ -54,19 +54,19 @@ public class ArmatureNode extends Node { /** * The armature to be displayed. */ - final private Armature armature; + private final Armature armature; /** * The map between the bone index and its length. */ - final private Map jointToGeoms = new HashMap<>(); - final private Map geomToJoint = new HashMap<>(); + private final Map jointToGeoms = new HashMap<>(); + private final Map geomToJoint = new HashMap<>(); private Joint selectedJoint = null; - final private Vector3f tmp = new Vector3f(); - final private Vector2f tmpv2 = new Vector2f(); - private final static ColorRGBA selectedColor = ColorRGBA.Orange; - private final static ColorRGBA selectedColorJ = ColorRGBA.Yellow; - private final static ColorRGBA outlineColor = ColorRGBA.LightGray; - private final static ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + private final Vector3f tmp = new Vector3f(); + private final Vector2f tmpv2 = new Vector2f(); + private static final ColorRGBA selectedColor = ColorRGBA.Orange; + private static final ColorRGBA selectedColorJ = ColorRGBA.Yellow; + private static final ColorRGBA outlineColor = ColorRGBA.LightGray; + private static final ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); private Camera camera; diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java index 266607eb3d..989667eb23 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java @@ -35,10 +35,8 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer.Type; - public class JointShape extends Mesh { - /** * Serialization only. Do not use. */ @@ -74,6 +72,4 @@ protected JointShape() { updateBound(); setStatic(); } - - } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java index ba3361c928..f7f66a2b04 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java @@ -43,7 +43,7 @@ */ public class IndexByteBuffer extends IndexBuffer { - final private ByteBuffer buf; + private final ByteBuffer buf; /** * the largest index value that can be put to the buffer */ diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java index aeec327e16..c8d691589f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java @@ -43,7 +43,7 @@ */ public class IndexIntBuffer extends IndexBuffer { - final private IntBuffer buf; + private final IntBuffer buf; public IndexIntBuffer(IntBuffer buffer) { buf = buffer; diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java index 6fd3253c6a..c505947eb9 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java @@ -43,7 +43,7 @@ */ public class IndexShortBuffer extends IndexBuffer { - final private ShortBuffer buf; + private final ShortBuffer buf; /** * the largest index value that can be put to the buffer */ diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java index 3299de5ce2..4b65a05abe 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java @@ -10,7 +10,7 @@ import java.util.Map; public class MorphTarget implements Savable { - final private EnumMap buffers = new EnumMap<>(VertexBuffer.Type.class); + private final EnumMap buffers = new EnumMap<>(VertexBuffer.Type.class); private String name = null; public MorphTarget() { 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 fbd1350eb8..425f0ad0bc 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -50,7 +50,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { /** * the indentation characters 1à tabulation characters */ - private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + private static final String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; protected ShaderNodeVariable inPosTmp; diff --git a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java index 1a6ab75ca6..8d55ab4430 100644 --- a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java +++ b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java @@ -55,31 +55,31 @@ public class UniformBindingManager { private float near, far; private Float time, tpf; private int viewX, viewY, viewWidth, viewHeight; - final private Vector3f camUp = new Vector3f(), + private final Vector3f camUp = new Vector3f(), camLeft = new Vector3f(), camDir = new Vector3f(), camLoc = new Vector3f(); - final private Matrix4f tempMatrix = new Matrix4f(); - final private Matrix4f viewMatrix = new Matrix4f(); - final private Matrix4f projMatrix = new Matrix4f(); - final private Matrix4f viewProjMatrix = new Matrix4f(); - final private Matrix4f worldMatrix = new Matrix4f(); - final private Matrix4f worldViewMatrix = new Matrix4f(); - final private Matrix4f worldViewProjMatrix = new Matrix4f(); - final private Matrix3f normalMatrix = new Matrix3f(); - final private Matrix3f worldNormalMatrix = new Matrix3f(); - final private Matrix4f worldMatrixInv = new Matrix4f(); - final private Matrix3f worldMatrixInvTrsp = new Matrix3f(); - final private Matrix4f viewMatrixInv = new Matrix4f(); - final private Matrix4f projMatrixInv = new Matrix4f(); - final private Matrix4f viewProjMatrixInv = new Matrix4f(); - final private Matrix4f worldViewMatrixInv = new Matrix4f(); - final private Matrix3f normalMatrixInv = new Matrix3f(); - final private Matrix4f worldViewProjMatrixInv = new Matrix4f(); - final private Vector4f viewPort = new Vector4f(); - final private Vector2f resolution = new Vector2f(); - final private Vector2f resolutionInv = new Vector2f(); - final private Vector2f nearFar = new Vector2f(); + private final Matrix4f tempMatrix = new Matrix4f(); + private final Matrix4f viewMatrix = new Matrix4f(); + private final Matrix4f projMatrix = new Matrix4f(); + private final Matrix4f viewProjMatrix = new Matrix4f(); + private final Matrix4f worldMatrix = new Matrix4f(); + private final Matrix4f worldViewMatrix = new Matrix4f(); + private final Matrix4f worldViewProjMatrix = new Matrix4f(); + private final Matrix3f normalMatrix = new Matrix3f(); + private final Matrix3f worldNormalMatrix = new Matrix3f(); + private final Matrix4f worldMatrixInv = new Matrix4f(); + private final Matrix3f worldMatrixInvTrsp = new Matrix3f(); + private final Matrix4f viewMatrixInv = new Matrix4f(); + private final Matrix4f projMatrixInv = new Matrix4f(); + private final Matrix4f viewProjMatrixInv = new Matrix4f(); + private final Matrix4f worldViewMatrixInv = new Matrix4f(); + private final Matrix3f normalMatrixInv = new Matrix3f(); + private final Matrix4f worldViewProjMatrixInv = new Matrix4f(); + private final Vector4f viewPort = new Vector4f(); + private final Vector2f resolution = new Vector2f(); + private final Vector2f resolutionInv = new Vector2f(); + private final Vector2f nearFar = new Vector2f(); /** * Internal use only. diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java index 78198ad025..f8edf9a203 100644 --- a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java @@ -84,8 +84,7 @@ protected ObjectSerializer getSerializer(Object obj) { /** * Register a serializer - * - * @param type + * @param serializer An object of type {@link ObjectSerializer} */ protected void registerSerializer(ObjectSerializer serializer) { serializers.add(serializer); @@ -98,6 +97,7 @@ protected void registerSerializer(ObjectSerializer serializer) { * the object to serialize * @return the size */ + @SuppressWarnings("unchecked") public int estimateSize(Object o) { ObjectSerializer s = getSerializer(o); return s.length(this, o); @@ -109,7 +109,7 @@ public int estimateSize(Object o) { * the object to serialize * @return the basic alignment */ - + @SuppressWarnings("unchecked") public int getBasicAlignment(Object o) { ObjectSerializer s = getSerializer(o); return s.basicAlignment(this, o); @@ -137,6 +137,7 @@ public int align(int pos, int basicAlignment) { * @param o * the Object to serialize */ + @SuppressWarnings("unchecked") public void write(ByteBuffer out, Object o) { ObjectSerializer s = getSerializer(o); s.write(this, out, o); 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 886abc45ec..3f41721b3c 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java @@ -64,17 +64,17 @@ public class BasicShadowRenderer implements SceneProcessor { private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); private RenderManager renderManager; private ViewPort viewPort; - final private FrameBuffer shadowFB; - final private Texture2D shadowMap; - final private Camera shadowCam; - final private Material preshadowMat; - final private Material postshadowMat; - final private Picture dispPic = new Picture("Picture"); + private final FrameBuffer shadowFB; + private final Texture2D shadowMap; + private final Camera shadowCam; + private final Material preshadowMat; + private final Material postshadowMat; + private final Picture dispPic = new Picture("Picture"); private boolean noOccluders = false; - final private Vector3f[] points = new Vector3f[8]; - final private Vector3f direction = new Vector3f(); + private final Vector3f[] points = new Vector3f[8]; + private final Vector3f direction = new Vector3f(); protected Texture2D dummyTex; - final private float shadowMapSize; + private final float shadowMapSize; protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); protected GeometryList shadowOccluders = new GeometryList(new OpaqueComparator()); 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 4cefa6ab6f..573652e4cc 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -80,7 +80,7 @@ public class FrameBuffer extends NativeObject { private int width = 0; private int height = 0; private int samples = 1; - final private ArrayList colorBufs = new ArrayList<>(); + private final ArrayList colorBufs = new ArrayList<>(); private RenderBuffer depthBuf = null; private int colorBufIndex = 0; private boolean srgb; diff --git a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java index b902fb6d6d..535f91d4ed 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -65,8 +65,8 @@ public final class BufferUtils { private static final BufferAllocator allocator = BufferAllocatorFactory.create(); private static boolean trackDirectMemory = false; - final private static ReferenceQueue removeCollected = new ReferenceQueue(); - final private static ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); + private static final ReferenceQueue removeCollected = new ReferenceQueue(); + private static final ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); static ClearReferences cleanupthread; /** diff --git a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java index a582da4232..bd21d58eb4 100644 --- a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java +++ b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java @@ -45,11 +45,11 @@ */ public class JmeFormatter extends Formatter { - final private Date calendar = new Date(); - final private String lineSeparator; - final private MessageFormat format; - final private Object args[] = new Object[1]; - final private StringBuffer store = new StringBuffer(); + private final Date calendar = new Date(); + private final String lineSeparator; + private final MessageFormat format; + private final Object args[] = new Object[1]; + private final StringBuffer store = new StringBuffer(); public JmeFormatter(){ lineSeparator = System.getProperty("line.separator"); @@ -80,7 +80,7 @@ public String format(LogRecord record) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); + record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { diff --git a/jme3-core/src/main/java/com/jme3/util/ListMap.java b/jme3-core/src/main/java/com/jme3/util/ListMap.java index 6fd6ee55e6..0295c0c950 100644 --- a/jme3-core/src/main/java/com/jme3/util/ListMap.java +++ b/jme3-core/src/main/java/com/jme3/util/ListMap.java @@ -43,7 +43,7 @@ */ public final class ListMap extends AbstractMap implements Cloneable, Serializable { - private final static class ListMapEntry implements Map.Entry, Cloneable { + private static final class ListMapEntry implements Map.Entry, Cloneable { private final K key; private V value; diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java index c35b91145d..fed89068f6 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java @@ -74,12 +74,12 @@ public class NativeObjectManager { /** * List of currently active GLObjects. */ - final private HashMap refMap = new HashMap<>(); + private final HashMap refMap = new HashMap<>(); /** * List of real objects requested by user for deletion. */ - final private ArrayDeque userDeletionQueue = new ArrayDeque<>(); + private final ArrayDeque userDeletionQueue = new ArrayDeque<>(); private static class NativeObjectRef extends PhantomReference { diff --git a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java index 3bf9381526..432f51ad18 100644 --- a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java +++ b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java @@ -83,7 +83,7 @@ public class SafeArrayList implements List, Cloneable { // the list. This was because the callers couldn't remove a child // without it being detached properly, for example. - final private Class elementType; + private final Class elementType; private List buffer; private E[] backingArray; private int size = 0; 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 6afc55cdf3..7b85f77503 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 @@ -41,7 +41,7 @@ public class BlockLanguageParser { private Reader reader; - final private ArrayList statementStack = new ArrayList<>(); + private final ArrayList statementStack = new ArrayList<>(); private Statement lastStatement; private int lineNumber = 1; diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java index 5496fbb7bd..92981c2cb5 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java @@ -104,12 +104,12 @@ public class Cloner { /** * Keeps track of the objects that have been cloned so far. */ - final private IdentityHashMap index = new IdentityHashMap<>(); + private final IdentityHashMap index = new IdentityHashMap<>(); /** * Custom functions for cloning objects. */ - final private Map functions = new HashMap<>(); + private final Map functions = new HashMap<>(); /** * Cache the clone methods once for all cloners. diff --git a/jme3-core/src/main/java/com/jme3/util/functional/Function.java b/jme3-core/src/main/java/com/jme3/util/functional/Function.java index 4766ed9034..c4948072e3 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/Function.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/Function.java @@ -1,6 +1,36 @@ +/* + * Copyright (c) 2009-2024 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.functional; - public interface Function { R eval(T t); } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java index 0f8089d530..63dec2f682 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java @@ -1,4 +1,34 @@ - +/* + * Copyright (c) 2009-2024 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.functional; public interface NoArgFunction { diff --git a/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java index 014c6787d5..42dd416908 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java @@ -1,4 +1,34 @@ - +/* + * Copyright (c) 2009-2024 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.functional; public interface NoArgVoidFunction { diff --git a/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java index e7726b2c11..ccafb2e5c8 100644 --- a/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java @@ -1,4 +1,34 @@ - +/* + * Copyright (c) 2009-2024 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.functional; public interface VoidFunction { diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java index 0bb40e3736..a694023dde 100644 --- a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java @@ -61,11 +61,11 @@ */ public class MikktspaceTangentGenerator { - private final static int MARK_DEGENERATE = 1; - private final static int QUAD_ONE_DEGEN_TRI = 2; - private final static int GROUP_WITH_ANY = 4; - private final static int ORIENT_PRESERVING = 8; - private final static long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL; + private static final int MARK_DEGENERATE = 1; + private static final int QUAD_ONE_DEGEN_TRI = 2; + private static final int GROUP_WITH_ANY = 4; + private static final int ORIENT_PRESERVING = 8; + private static final long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL; static final int CELLS = 2048; private final static Logger logger = Logger.getLogger(MikktspaceTangentGenerator.class.getName()); diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java index 2bd904d398..65b25628c1 100644 --- a/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java @@ -39,11 +39,13 @@ public class SubStructArrayField extends StructField { + @SuppressWarnings("unchecked") public SubStructArrayField(int position, String name, T[] value) { super(position, name, value); initializeToZero((Class) value[0].getClass()); } + @SuppressWarnings("unchecked") public SubStructArrayField(int position, String name, int length, Class structClass) { super(position, name, (T[]) Array.newInstance(structClass, length)); initializeToZero(structClass); diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java index 6dad1ea566..b634e2abf1 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java @@ -47,7 +47,7 @@ */ public class UrlAssetInfo extends AssetInfo { - final private URL url; + private final URL url; private InputStream in; public static UrlAssetInfo create(AssetManager assetManager, AssetKey key, URL url) throws IOException { 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 7b6c6ca79e..cdaf9abc17 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 @@ -124,17 +124,17 @@ public class BinaryExporter implements JmeExporter { protected int aliasCount = 1; protected int idCount = 1; - final private IdentityHashMap contentTable + private final IdentityHashMap contentTable = new IdentityHashMap<>(); protected HashMap locationTable = new HashMap<>(); // key - class name, value = bco - final private HashMap classes + private final HashMap classes = new HashMap<>(); - final private ArrayList contentKeys = new ArrayList<>(); + private final ArrayList contentKeys = new ArrayList<>(); public static boolean debug = false; public static boolean useFastBufs = true; 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 0be52a8192..03c54823de 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 @@ -54,16 +54,16 @@ public final class BinaryImporter implements JmeImporter { private AssetManager assetManager; //Key - alias, object - bco - final private HashMap classes + private final HashMap classes = new HashMap<>(); //Key - id, object - the savable - final private HashMap contentTable + private final HashMap contentTable = new HashMap<>(); //Key - savable, object - capsule - final private IdentityHashMap capsuleTable + private final IdentityHashMap capsuleTable = new IdentityHashMap<>(); //Key - id, object - location in the file - final private HashMap locationTable + private final HashMap locationTable = new HashMap<>(); public static boolean debug = false; 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 0a0c0403c2..5c82c91b6f 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 @@ -51,8 +51,6 @@ import com.jme3.util.blockparser.Statement; import com.jme3.util.clone.Cloner; import jme3tools.shader.Preprocessor; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -76,10 +74,10 @@ public class J3MLoader implements AssetLoader { private Material material; private TechniqueDef technique; private RenderState renderState; - final private ArrayList presetDefines = new ArrayList<>(); + private final ArrayList presetDefines = new ArrayList<>(); - final private List> shaderLanguages; - final private EnumMap shaderNames; + private final List> shaderLanguages; + private final EnumMap shaderNames; private static final String whitespacePattern = "\\p{javaWhitespace}+"; diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java index 2e229c2d8e..63b8ba68c7 100644 --- a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java @@ -43,7 +43,7 @@ public class GLSLLoader implements AssetLoader { private AssetManager assetManager; - final private Map dependCache = new HashMap<>(); + private final Map dependCache = new HashMap<>(); /** * Used to load {@link ShaderDependencyNode}s. diff --git a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java index 2a24c5e1b5..648f85f070 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java @@ -108,7 +108,7 @@ public class LodGenerator { private List triangleList; private List vertexList = new ArrayList<>(); private float meshBoundingSphereRadius; - final private Mesh mesh; + private final Mesh mesh; /** * Enumerate criteria for removing triangles. @@ -235,7 +235,7 @@ public String toString() { /** * Comparator used to sort vertices according to their collapse cost */ - final private Comparator collapseComparator = new Comparator() { + private final Comparator collapseComparator = new Comparator() { @Override public int compare(Vertex o1, Vertex o2) { if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) { diff --git a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java index feec784320..9447b2d956 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java @@ -121,11 +121,11 @@ public class TextureAtlas { private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName()); private Map images; - final private int atlasWidth, atlasHeight; - final private Format format = Format.ABGR8; - final private Node root; - final private Map locationMap; - final private Map mapNameMap; + private final int atlasWidth, atlasHeight; + private final Format format = Format.ABGR8; + private final Node root; + private final Map locationMap; + private final Map mapNameMap; private String rootMapName; public TextureAtlas(int width, int height) { From a9fec64ca8690f44b1595bdd91da6e0efca0717a Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Tue, 30 Jul 2024 11:29:36 -0400 Subject: [PATCH 38/57] Upgrade spotbugs to 4.8.6 (#2295) * Upgrade spotbugs * Upgrade spotbugs --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7b47851851..6e8e919e52 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.2.0' classpath 'me.tatarka:gradle-retrolambda:3.7.1' - classpath "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.5.1" + classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.0.18" } } @@ -49,6 +49,7 @@ subprojects { // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical. spotbugs { ignoreFailures = true + toolVersion = '4.8.6' } tasks.withType(com.github.spotbugs.snom.SpotBugsTask ) { From 1e10225f746b5c1ffe3eaca6d9f69c1421ea403f Mon Sep 17 00:00:00 2001 From: Juan Cruz Fandino Date: Wed, 31 Jul 2024 07:20:09 +0200 Subject: [PATCH 39/57] BatchNode: Fix IndexOutOfBoundsException (#2297) - There are temporary arrays that are reused to work with vertex buffers. - Some conditions cause the index to be used for storing more vertices that they can hold. - This throws the exception. - This fix validates the arrays size before accessing them, recreating the arrays if they are too small. --- .../main/java/com/jme3/scene/BatchNode.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 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 42034ea047..883090c7aa 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -246,13 +246,7 @@ protected void doBatch() { //init the temp arrays if something has been batched only. if (matMap.size() > 0) { - //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed. - //init temp float arrays - tmpFloat = new float[maxVertCount * 3]; - tmpFloatN = new float[maxVertCount * 3]; - if (useTangents) { - tmpFloatT = new float[maxVertCount * 4]; - } + initTempFloatArrays(); } } @@ -387,7 +381,6 @@ private void mergeGeometries(Mesh outMesh, List geometries) { int maxWeights = -1; Mesh.Mode mode = null; - float lineWidth = 1f; for (Geometry geom : geometries) { totalVerts += geom.getVertexCount(); totalTris += geom.getTriangleCount(); @@ -537,6 +530,7 @@ private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, Float Vector3f norm = vars.vect2; Vector3f tan = vars.vect3; + validateTempFloatArrays(end - start); int length = (end - start) * 3; int tanLength = (end - start) * 4; @@ -617,6 +611,22 @@ private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, Float } } + private void validateTempFloatArrays(int vertCount) { + if (maxVertCount < vertCount) { + maxVertCount = vertCount; + initTempFloatArrays(); + } + } + + private void initTempFloatArrays() { + //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed. + tmpFloat = new float[maxVertCount * 3]; + tmpFloatN = new float[maxVertCount * 3]; + if (useTangents) { + tmpFloatT = new float[maxVertCount * 4]; + } + } + private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { TempVars vars = TempVars.get(); Vector3f pos = vars.vect1; From bc346ed24645692d2314e278a19ba59738ac5a7d Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 17 Aug 2024 14:57:02 -0700 Subject: [PATCH 40/57] README.md: add a weblink to the High Impact game (at Steam) (#2301) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 885d2d1d29..aab2582e50 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The engine is used by several commercial game studios and computer-science cours - [Stranded (on Itch)](https://tgiant.itch.io/stranded) - [The Afflicted Forests (Coming Soon to Steam)](https://www.indiedb.com/games/the-afflicted-forests) - [Star Colony: Beyond Horizons (on Google Play)](https://play.google.com/store/apps/details?id=game.colony.ColonyBuilder) + - [High Impact (on Steam)](https://store.steampowered.com/app/3059050/High_Impact/) ## Getting Started From e2e9a94f08e171779c6518c11e7d22d73969f775 Mon Sep 17 00:00:00 2001 From: richardTingle <6330028+richardTingle@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:00:47 +0100 Subject: [PATCH 41/57] #2284 Double check the collision really has happened in world space (#2285) (otherwise glancing blows can end up "happening" but at infinite distance due to numerical precision) --- .../java/com/jme3/collision/bih/BIHNode.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java index 6f422baed3..12bf88b23a 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java @@ -411,15 +411,20 @@ public final int intersectWhere(Ray r, t = t_world; } - Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); - Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); - float worldSpaceDist = o.distance(contactPoint); - - CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); - cr.setContactNormal(contactNormal); - cr.setTriangleIndex(tree.getTriangleIndex(i)); - results.addCollision(cr); - cols++; + // this second isInfinite test is unlikely to fail but due to numeric precision it might + // be the case that in local coordinates it just hits and in world coordinates it just misses + // this filters those cases out (treating them as misses). + if (!Float.isInfinite(t)){ + Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); + Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); + float worldSpaceDist = o.distance(contactPoint); + + CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); + cr.setContactNormal(contactNormal); + cr.setTriangleIndex(tree.getTriangleIndex(i)); + results.addCollision(cr); + cols++; + } } } } From c0b369bfd1c8cca326c9fd36b23e17e590358fe4 Mon Sep 17 00:00:00 2001 From: rickard Date: Thu, 22 Aug 2024 22:11:03 +0200 Subject: [PATCH 42/57] Minor refactor to allow for saving AnimLayers --- .../main/java/com/jme3/anim/AnimComposer.java | 10 ++-- .../main/java/com/jme3/anim/AnimLayer.java | 46 +++++++++++++------ .../java/com/jme3/anim/AnimationMask.java | 4 +- .../main/java/com/jme3/anim/ArmatureMask.java | 19 +++++++- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index d06b0816cc..aaa3a80fb4 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -66,7 +66,9 @@ public class AnimComposer extends AbstractControl { * Instantiate a composer with a single layer, no actions, and no clips. */ public AnimComposer() { - layers.put(DEFAULT_LAYER, new AnimLayer(this, DEFAULT_LAYER, null)); + if(layers.isEmpty()) { + layers.put(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); + } } /** @@ -314,7 +316,7 @@ public Action removeAction(String name) { * @param mask the desired mask for the new layer (alias created) */ public void makeLayer(String name, AnimationMask mask) { - AnimLayer l = new AnimLayer(this, name, mask); + AnimLayer l = new AnimLayer(name, mask); layers.put(name, l); } @@ -399,7 +401,7 @@ public Set getAnimClipsNames() { @Override protected void controlUpdate(float tpf) { for (AnimLayer layer : layers.values()) { - layer.update(tpf); + layer.update(tpf, globalSpeed); } } @@ -542,6 +544,7 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); animClipMap = (Map) ic.readStringSavableMap("animClipMap", new HashMap()); globalSpeed = ic.readFloat("globalSpeed", 1f); + layers = (Map) ic.readStringSavableMap("layers", new HashMap()); } /** @@ -557,5 +560,6 @@ public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap()); oc.write(globalSpeed, "globalSpeed", 1f); + oc.writeStringSavableMap(layers, "layers", new HashMap()); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java index 0c3c97e998..aaf277fb4d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -32,8 +32,14 @@ package com.jme3.anim; import com.jme3.anim.tween.action.Action; +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.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; +import java.io.IOException; /** * A named portion of an AnimComposer that can run (at most) one Action at a @@ -48,7 +54,7 @@ *

Animation time may advance at a different rate from application time, * based on speedup factors in the composer and the current Action. */ -public class AnimLayer implements JmeCloneable { +public class AnimLayer implements JmeCloneable, Savable { /** * The Action currently running on this layer, or null if none. */ @@ -57,16 +63,12 @@ public class AnimLayer implements JmeCloneable { * The name of Action currently running on this layer, or null if none. */ private String currentActionName; - /** - * The composer that owns this layer. Were it not for cloning, this field - * would be final. - */ - private AnimComposer composer; + /** * Limits the portion of the model animated by this layer. If null, this * layer can animate the entire model. */ - private final AnimationMask mask; + private AnimationMask mask; /** * The current animation time, in scaled seconds. Always non-negative. */ @@ -79,9 +81,13 @@ public class AnimLayer implements JmeCloneable { /** * The name of this layer. */ - final private String name; + private String name; private boolean loop = true; + + protected AnimLayer() { + + } /** * Instantiates a layer without a manager or a current Action, owned by the @@ -92,10 +98,7 @@ public class AnimLayer implements JmeCloneable { * @param mask the AnimationMask (alias created) or null to allow this layer * to animate the entire model */ - AnimLayer(AnimComposer composer, String name, AnimationMask mask) { - assert composer != null; - this.composer = composer; - + AnimLayer(String name, AnimationMask mask) { assert name != null; this.name = name; @@ -249,13 +252,13 @@ public void setLooping(boolean loop) { * @param appDeltaTimeInSeconds the amount application time to advance the * current Action, in seconds */ - void update(float appDeltaTimeInSeconds) { + void update(float appDeltaTimeInSeconds, float speed) { Action action = currentAction; if (action == null) { return; } - double speedup = action.getSpeed() * composer.getGlobalSpeed(); + double speedup = action.getSpeed() * speed; double scaledDeltaTime = speedup * appDeltaTimeInSeconds; time += scaledDeltaTime; @@ -292,7 +295,6 @@ void update(float appDeltaTimeInSeconds) { */ @Override public void cloneFields(Cloner cloner, Object original) { - composer = cloner.clone(composer); currentAction = null; currentActionName = null; } @@ -306,4 +308,18 @@ public Object jmeClone() { throw new AssertionError(); } } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + oc.write(mask, "mask", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + name = ic.readString("name", null); + mask = (AnimationMask) ic.readSavable("mask", null); + } } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java index bcfd77f0a1..8ab52c44fe 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java @@ -31,12 +31,14 @@ */ package com.jme3.anim; +import com.jme3.export.Savable; + /** * Created by Nehon * An AnimationMask is defining a subset of elements on which an animation will be applied. * Most used implementation is the ArmatureMask that defines a subset of joints in an Armature. */ -public interface AnimationMask { +public interface AnimationMask extends Savable { /** * Test whether the animation should be applied to the specified element. diff --git a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java index 7f889fc042..53780adf94 100644 --- a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java +++ b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java @@ -31,6 +31,11 @@ */ package com.jme3.anim; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; import java.util.BitSet; /** @@ -38,7 +43,7 @@ */ public class ArmatureMask implements AnimationMask { - final private BitSet affectedJoints = new BitSet(); + private BitSet affectedJoints = new BitSet(); /** * Instantiate a mask that affects no joints. @@ -206,4 +211,16 @@ public ArmatureMask removeAncestors(Joint start) { return this; } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(affectedJoints, "affectedJoints", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + affectedJoints = ic.readBitSet("affectedJoints", null); + } } From eb0fb5f1bbbc49923b48a94a481aace521eac94e Mon Sep 17 00:00:00 2001 From: rickard Date: Fri, 23 Aug 2024 18:46:43 +0200 Subject: [PATCH 43/57] add a few tests --- .../java/com/jme3/anim/AnimComposerTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java b/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java index 331784352d..990b5c0051 100644 --- a/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java +++ b/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java @@ -31,6 +31,11 @@ */ package com.jme3.anim; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.tween.action.ClipAction; +import com.jme3.export.JmeExporter; +import java.util.Set; +import java.util.TreeSet; import org.junit.Assert; import org.junit.Test; @@ -54,6 +59,36 @@ public void testGetAnimClipsNames() { Assert.assertNotNull(composer.getAnimClipsNames()); Assert.assertEquals(0, composer.getAnimClipsNames().size()); } + + @Test + public void testMakeLayer() { + AnimComposer composer = new AnimComposer(); + + final String layerName = "TestLayer"; + + composer.makeLayer(layerName, null); + + final Set layers = new TreeSet<>(); + layers.add("Default"); + layers.add(layerName); + + Assert.assertNotNull(composer.getLayer(layerName)); + Assert.assertEquals(layers, composer.getLayerNames()); + } + + @Test + public void testMakeAction() { + AnimComposer composer = new AnimComposer(); + + final String animName = "TestClip"; + + final AnimClip anim = new AnimClip(animName); + composer.addAnimClip(anim); + + final Action action = composer.makeAction(animName); + + Assert.assertNotNull(action); + } @Test(expected = UnsupportedOperationException.class) public void testGetAnimClipsIsNotModifiable() { From 678bd441662e0230097a96abe3ac16c2aaf9b908 Mon Sep 17 00:00:00 2001 From: rickard Date: Sat, 24 Aug 2024 21:24:29 +0200 Subject: [PATCH 44/57] move Savable to ArmatureMask clean up unused imports --- jme3-core/src/main/java/com/jme3/anim/AnimLayer.java | 4 +++- jme3-core/src/main/java/com/jme3/anim/AnimationMask.java | 4 +--- jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java | 3 ++- jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java | 2 -- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java index aaf277fb4d..00d81dfa35 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -313,7 +313,9 @@ public Object jmeClone() { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); - oc.write(mask, "mask", null); + if(mask instanceof ArmatureMask) { + oc.write((ArmatureMask) mask, "mask", null); + } } @Override diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java index 8ab52c44fe..bcfd77f0a1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java @@ -31,14 +31,12 @@ */ package com.jme3.anim; -import com.jme3.export.Savable; - /** * Created by Nehon * An AnimationMask is defining a subset of elements on which an animation will be applied. * Most used implementation is the ArmatureMask that defines a subset of joints in an Armature. */ -public interface AnimationMask extends Savable { +public interface AnimationMask { /** * Test whether the animation should be applied to the specified element. diff --git a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java index 53780adf94..bf8e10155d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java +++ b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java @@ -35,13 +35,14 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import java.io.IOException; import java.util.BitSet; /** * An AnimationMask to select joints from a single Armature. */ -public class ArmatureMask implements AnimationMask { +public class ArmatureMask implements AnimationMask, Savable { private BitSet affectedJoints = new BitSet(); diff --git a/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java b/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java index 990b5c0051..4cc5eb9080 100644 --- a/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java +++ b/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java @@ -32,8 +32,6 @@ package com.jme3.anim; import com.jme3.anim.tween.action.Action; -import com.jme3.anim.tween.action.ClipAction; -import com.jme3.export.JmeExporter; import java.util.Set; import java.util.TreeSet; import org.junit.Assert; From 420994a1c89283a6e06ee293299e9506c75f10e4 Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 25 Aug 2024 19:50:48 +0200 Subject: [PATCH 45/57] check if instance is of Savable instead for wider compatibility clean up a few rows --- jme3-core/src/main/java/com/jme3/anim/AnimComposer.java | 4 +--- jme3-core/src/main/java/com/jme3/anim/AnimLayer.java | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index aaa3a80fb4..f9cdf81167 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -66,9 +66,7 @@ public class AnimComposer extends AbstractControl { * Instantiate a composer with a single layer, no actions, and no clips. */ public AnimComposer() { - if(layers.isEmpty()) { - layers.put(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); - } + layers.put(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); } /** diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java index 00d81dfa35..2baad50c47 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -313,8 +313,8 @@ public Object jmeClone() { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); - if(mask instanceof ArmatureMask) { - oc.write((ArmatureMask) mask, "mask", null); + if(mask instanceof Savable) { + oc.write((Savable) mask, "mask", null); } } From 8c31b05127d30f4687971e0d33b236d9ed52ea3c Mon Sep 17 00:00:00 2001 From: rickard Date: Mon, 26 Aug 2024 15:35:26 +0200 Subject: [PATCH 46/57] review fixes --- jme3-core/src/main/java/com/jme3/anim/AnimLayer.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java index 2baad50c47..6cd79e731d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -85,6 +85,9 @@ public class AnimLayer implements JmeCloneable, Savable { private boolean loop = true; + /** + * For serialization only. Do not use. + */ protected AnimLayer() { } @@ -93,7 +96,6 @@ protected AnimLayer() { * Instantiates a layer without a manager or a current Action, owned by the * specified composer. * - * @param composer the owner (not null, alias created) * @param name the layer name (not null) * @param mask the AnimationMask (alias created) or null to allow this layer * to animate the entire model @@ -251,14 +253,15 @@ public void setLooping(boolean loop) { * * @param appDeltaTimeInSeconds the amount application time to advance the * current Action, in seconds + * @param globalSpeed the global speed applied to all layers. */ - void update(float appDeltaTimeInSeconds, float speed) { + void update(float appDeltaTimeInSeconds, float globalSpeed) { Action action = currentAction; if (action == null) { return; } - double speedup = action.getSpeed() * speed; + double speedup = action.getSpeed() * globalSpeed; double scaledDeltaTime = speedup * appDeltaTimeInSeconds; time += scaledDeltaTime; @@ -313,7 +316,7 @@ public Object jmeClone() { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); - if(mask instanceof Savable) { + if (mask instanceof Savable) { oc.write((Savable) mask, "mask", null); } } From 6709ee8dc65691aa31cbdb33e11ebd6aa90f8ec0 Mon Sep 17 00:00:00 2001 From: Night Rider <97632588+JNightRider@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:19:25 -0600 Subject: [PATCH 47/57] Fix bug #2277 - GltfLoader (#2309) * fix bug #2277 * update * script error * module implementation * corrections... --------- Co-authored-by: wil --- jme3-examples/build.gradle | 4 ++-- .../com/jme3/plugins/gson/GsonElement.java | 19 ++++++++++++++++++ .../com/jme3/plugins/json/JsonElement.java | 1 - jme3-plugins/build.gradle | 4 +++- .../plugins/gltf/CustomContentManager.java | 4 ++-- .../scene/plugins/gltf/ExtensionLoader.java | 2 +- .../jme3/scene/plugins/gltf/ExtrasLoader.java | 2 +- .../jme3/scene/plugins/gltf/GltfLoader.java | 12 +++++------ .../jme3/scene/plugins/gltf/GltfUtils.java | 20 +++++++++++++++---- .../gltf/LightsPunctualExtensionLoader.java | 6 +++--- .../PBREmissiveStrengthExtensionLoader.java | 2 +- .../gltf/PBRSpecGlossExtensionLoader.java | 2 +- .../gltf/TextureTransformExtensionLoader.java | 6 +++--- .../plugins/gltf/UnlitExtensionLoader.java | 2 +- .../scene/plugins/gltf/UserDataLoader.java | 8 ++++---- 15 files changed, 62 insertions(+), 32 deletions(-) diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index c51b2ae522..b5979feceb 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -24,8 +24,8 @@ dependencies { implementation project(':jme3-networking') implementation project(':jme3-niftygui') implementation project(':jme3-plugins') -// implementation project(':jme3-plugins-json') -// implementation project(':jme3-plugins-json-gson') + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') implementation project(':jme3-terrain') implementation project(':jme3-awt-dialogs') runtimeOnly project(':jme3-testdata') diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java index 6e929be9d4..102969c65f 100644 --- a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java @@ -36,6 +36,8 @@ import com.jme3.plugins.json.JsonObject; import com.jme3.plugins.json.JsonPrimitive; +import java.util.Objects; + /** * GSON implementation of {@link JsonElement} */ @@ -46,6 +48,23 @@ class GsonElement implements JsonElement { this.element = element; } + @Override + public int hashCode() { + return Objects.hashCode(this.element); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final GsonElement other = (GsonElement) obj; + return Objects.equals(this.element, other.element); + } + protected boolean isNull(com.google.gson.JsonElement element) { if (element == null) return true; if (element.isJsonNull()) return true; diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java index e47f94389d..15894147a9 100644 --- a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java @@ -98,5 +98,4 @@ public interface JsonElement { * @return the casted JsonElement */ public T autoCast(); - } diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index b88f0a64fd..e84f234a68 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -11,6 +11,8 @@ sourceSets { dependencies { api project(':jme3-core') - api 'com.google.code.gson:gson:2.9.1' + + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') testRuntimeOnly project(':jme3-desktop') } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java index df612f1a0a..20f2c5e141 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java @@ -31,8 +31,8 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; import com.jme3.asset.AssetLoadException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java index c3bd71f394..1778cece97 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java @@ -31,7 +31,7 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; import java.io.IOException; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java index c470478db1..1267cddf05 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java @@ -31,7 +31,7 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; /** * Interface to handle a glTF extra. diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index e4e308355d..3002515633 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -31,12 +31,10 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; +import com.jme3.plugins.json.JsonElement; import com.jme3.anim.*; import com.jme3.asset.*; import com.jme3.material.Material; @@ -123,7 +121,7 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws defaultMat.setFloat("Roughness", 1f); } - docRoot = JsonParser.parseReader(new JsonReader(new InputStreamReader(stream))).getAsJsonObject(); + docRoot = parse(stream); JsonObject asset = docRoot.getAsJsonObject().get("asset").getAsJsonObject(); getAsString(asset, "generator"); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 0af3321aab..8f015f28db 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -31,12 +31,14 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; import com.jme3.math.*; +import com.jme3.plugins.json.Json; +import com.jme3.plugins.json.JsonParser; import com.jme3.scene.*; import com.jme3.texture.Texture; import com.jme3.util.*; @@ -58,8 +60,18 @@ public class GltfUtils { */ private GltfUtils() { } + + /** + * Parse a json input stream and returns a {@link JsonObject} + * @param stream the stream to parse + * @return the JsonObject + */ + public static JsonObject parse(InputStream stream) { + JsonParser parser = Json.create(); + return parser.parse(stream); + } - public static Mesh.Mode getMeshMode(Integer mode) { + public static Mesh.Mode getMeshMode(Integer mode) { if (mode == null) { return Mesh.Mode.Triangles; } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java index 26d664ed82..5b55c49b2e 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java @@ -31,9 +31,9 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetLoadException; import com.jme3.light.DirectionalLight; import com.jme3.light.Light; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java index c08d132307..71ab1b84a4 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java @@ -32,7 +32,7 @@ package com.jme3.scene.plugins.gltf; import com.jme3.asset.AssetKey; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; import java.io.IOException; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java index 7eca101de3..abeda10cd7 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java @@ -34,7 +34,7 @@ import com.jme3.asset.AssetKey; import java.io.IOException; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsColor; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index ecbcd2519f..aff6fbdcf1 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -31,9 +31,9 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetLoadException; import com.jme3.math.Matrix3f; import com.jme3.math.Vector3f; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java index 620c7f7e45..790d70b0cf 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java @@ -31,7 +31,7 @@ */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonElement; import com.jme3.asset.AssetKey; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java index 5045f71b1b..fc30611a6f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java @@ -32,10 +32,10 @@ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; import com.jme3.scene.Spatial; import java.lang.reflect.Array; From d71feba8d312e26cec0a49cde68d1bb588f81454 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Thu, 5 Sep 2024 01:14:53 -0400 Subject: [PATCH 48/57] Add reflectivity/shininess support to TerrainLighting.frag (#2306) * Add reflectivity/shininess support to TerrainLighting.frag Uses the (previously unused) SpecularMap as a gray-scale texture for painting shininess/reflectivity on the whole terrain. * Update TerrainLighting.j3md add USE_SPECULARMAP_AS_SHININESS define to make this PR cleaner, and allow the SpecularMap to be used as regular specularColor if USE_SPECULARMAP_AS_SHININESS is not true/defined * Update TerrainLighting.frag * Update TerrainLighting.frag --- .../MatDefs/Terrain/TerrainLighting.frag | 21 ++++++++++++++++--- .../MatDefs/Terrain/TerrainLighting.j3md | 5 +++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag index f2ca013fc7..8125982303 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag @@ -3,6 +3,10 @@ #import "Common/ShaderLib/Lighting.glsllib" uniform float m_Shininess; +#ifdef SPECULARMAP + uniform sampler2D m_SpecularMap; +#endif + uniform vec4 g_LightDirection; varying vec4 AmbientSum; @@ -634,6 +638,19 @@ void main(){ vec3 normal = vNormal; #endif + //----------------------- + // read shininess or specularColor from specularMap (possibly want to create a new texture called ShininessMap if there is ever a need to have both a specularMap and reflectivityMap) + //----------------------- + vec4 specularColor = vec4(1.0); + float finalShininessValue = m_Shininess; + #ifdef SPECULARMAP + vec4 specularMapColor = texture2D(m_SpecularMap, texCoord); + #ifdef USE_SPECULARMAP_AS_SHININESS + finalShininessValue = specularMapColor.r; //assumes that specularMap is a gray-scale reflectivity/shininess map) + #else + specularColor = specularMapColor; + #endif + #endif //----------------------- // lighting calculations @@ -641,9 +658,7 @@ void main(){ vec4 lightDir = vLightDir; lightDir.xyz = normalize(lightDir.xyz); - vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,m_Shininess); - - vec4 specularColor = vec4(1.0); + vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,finalShininessValue); //-------------------------- // final color calculations diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md index 341ef1c683..4eb9c8d7a2 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md @@ -103,6 +103,10 @@ MaterialDef Terrain Lighting { // The glow color of the object Color GlowColor + + // Use diffuse alpha when mixing + Boolean useSpecularMapAsShininess + } Technique { @@ -167,6 +171,7 @@ MaterialDef Terrain Lighting { DIFFUSEMAP_11_SCALE : DiffuseMap_11_scale USE_ALPHA : useDiffuseAlpha + USE_SPECULARMAP_AS_SHININESS : useSpecularMapAsShininess } } From 5f54eb2f56891a33135feec8d7a0616a2bdefb24 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Wed, 18 Sep 2024 15:27:21 -0700 Subject: [PATCH 49/57] buildscripts: centralize version numbers in a TOML catalog (#2311) --- build.gradle | 6 +- common.gradle | 13 ++-- gradle/libs.versions.toml | 50 ++++++++++++++ jme3-android-examples/build.gradle | 4 +- jme3-android/build.gradle | 4 +- jme3-examples/build.gradle | 2 +- jme3-jbullet/build.gradle | 4 +- jme3-jogg/build.gradle | 2 +- jme3-lwjgl/build.gradle | 6 +- jme3-lwjgl3/build.gradle | 103 +++++++++++++--------------- jme3-niftygui/build.gradle | 6 +- jme3-plugins-json-gson/build.gradle | 2 +- jme3-vr/build.gradle | 20 +++--- version.gradle | 2 +- 14 files changed, 132 insertions(+), 92 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/build.gradle b/build.gradle index 6e8e919e52..fbcc6f54a6 100644 --- a/build.gradle +++ b/build.gradle @@ -10,9 +10,9 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' - classpath 'me.tatarka:gradle-retrolambda:3.7.1' - classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.0.18" + classpath libs.android.build.gradle + classpath libs.gradle.retrolambda + classpath libs.spotbugs.gradle.plugin } } diff --git a/common.gradle b/common.gradle index d332cba6d7..9a623d47a5 100644 --- a/common.gradle +++ b/common.gradle @@ -29,11 +29,6 @@ tasks.withType(JavaCompile) { // compile-time options: } } -ext { - lwjgl3Version = '3.3.3' // used in both the jme3-lwjgl3 and jme3-vr build scripts - niftyVersion = '1.4.3' // used in both the jme3-niftygui and jme3-examples build scripts -} - repositories { mavenCentral() flatDir { @@ -43,9 +38,9 @@ repositories { dependencies { // Adding dependencies here will add the dependencies to each subproject. - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:3.12.4' - testImplementation 'org.codehaus.groovy:groovy-test:3.0.21' + testImplementation libs.junit4 + testImplementation libs.mokito.core + testImplementation libs.groovy.test } // Uncomment if you want to see the status of every test that is run and @@ -206,7 +201,7 @@ tasks.withType(Sign) { } checkstyle { - toolVersion '9.3' + toolVersion libs.versions.checkstyle.get() configFile file("${gradle.rootProject.rootDir}/config/checkstyle/checkstyle.xml") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..655e7e6a83 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,50 @@ +## catalog of libraries and plugins used to build the jmonkeyengine project + +[versions] + +checkstyle = "9.3" +lwjgl3 = "3.3.3" +nifty = "1.4.3" + +[libraries] + +android-build-gradle = "com.android.tools.build:gradle:4.2.0" +android-support-appcompat = "com.android.support:appcompat-v7:28.0.0" +androidx-annotation = "androidx.annotation:annotation:1.3.0" +androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.4.0" +gradle-git = "org.ajoberstar:gradle-git:1.2.0" +gradle-retrolambda = "me.tatarka:gradle-retrolambda:3.7.1" +groovy-test = "org.codehaus.groovy:groovy-test:3.0.21" +gson = "com.google.code.gson:gson:2.9.1" +j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.4" +jbullet = "com.github.stephengold:jbullet:1.0.3" +jinput = "net.java.jinput:jinput:2.0.9" +jna = "net.java.dev.jna:jna:5.10.0" +jnaerator-runtime = "com.nativelibs4java:jnaerator-runtime:0.12" +junit4 = "junit:junit:4.13.2" +lwjgl2 = "org.jmonkeyengine:lwjgl:2.9.5" +lwjgl3-awt = "org.lwjglx:lwjgl3-awt:0.1.8" + +lwjgl3-base = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl3" } +lwjgl3-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl3" } +lwjgl3-jawt = { module = "org.lwjgl:lwjgl-jawt", version.ref = "lwjgl3" } +lwjgl3-jemalloc = { module = "org.lwjgl:lwjgl-jemalloc", version.ref = "lwjgl3" } +lwjgl3-openal = { module = "org.lwjgl:lwjgl-openal", version.ref = "lwjgl3" } +lwjgl3-opencl = { module = "org.lwjgl:lwjgl-opencl", version.ref = "lwjgl3" } +lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" } +lwjgl3-openvr = { module = "org.lwjgl:lwjgl-openvr", version.ref = "lwjgl3" } +lwjgl3-ovr = { module = "org.lwjgl:lwjgl-ovr", version.ref = "lwjgl3" } + +mokito-core = "org.mockito:mockito-core:3.12.4" + +nifty = { module = "com.github.nifty-gui:nifty", version.ref = "nifty" } +nifty-default-controls = { module = "com.github.nifty-gui:nifty-default-controls", version.ref = "nifty" } +nifty-examples = { module = "com.github.nifty-gui:nifty-examples", version.ref = "nifty" } +nifty-style-black = { module = "com.github.nifty-gui:nifty-style-black", version.ref = "nifty" } + +spotbugs-gradle-plugin = "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.0.18" +vecmath = "javax.vecmath:vecmath:1.5.2" + +[bundles] + +[plugins] diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle index 9c2356ba7d..c17664e1dd 100644 --- a/jme3-android-examples/build.gradle +++ b/jme3-android-examples/build.gradle @@ -41,8 +41,8 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation 'junit:junit:4.13.2' - implementation 'com.android.support:appcompat-v7:28.0.0' + testImplementation libs.junit4 + implementation libs.android.support.appcompat implementation project(':jme3-core') implementation project(':jme3-android') diff --git a/jme3-android/build.gradle b/jme3-android/build.gradle index f0725dfc5e..4fa73b50ca 100644 --- a/jme3-android/build.gradle +++ b/jme3-android/build.gradle @@ -2,8 +2,8 @@ apply plugin: 'java' dependencies { //added annotations used by JmeSurfaceView. - compileOnly 'androidx.annotation:annotation:1.3.0' - compileOnly 'androidx.lifecycle:lifecycle-common:2.4.0' + compileOnly libs.androidx.annotation + compileOnly libs.androidx.lifecycle.common api project(':jme3-core') compileOnly 'android:android' } diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index b5979feceb..4f5d41b501 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation project(':jme3-terrain') implementation project(':jme3-awt-dialogs') runtimeOnly project(':jme3-testdata') - runtimeOnly "com.github.nifty-gui:nifty-examples:${niftyVersion}" // for the "all/intro.xml" example GUI + runtimeOnly libs.nifty.examples // for the "all/intro.xml" example GUI } jar.doFirst{ diff --git a/jme3-jbullet/build.gradle b/jme3-jbullet/build.gradle index 0258bb4d54..eb6ce8af88 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -14,8 +14,8 @@ sourceSets { } dependencies { - api 'com.github.stephengold:jbullet:1.0.3' - api 'javax.vecmath:vecmath:1.5.2' + api libs.jbullet + api libs.vecmath api project(':jme3-core') api project(':jme3-terrain') compileOnly project(':jme3-vr') //is selectively used if on classpath diff --git a/jme3-jogg/build.gradle b/jme3-jogg/build.gradle index 46f529116b..7bf0052577 100644 --- a/jme3-jogg/build.gradle +++ b/jme3-jogg/build.gradle @@ -1,4 +1,4 @@ dependencies { api project(':jme3-core') - api 'com.github.stephengold:j-ogg-vorbis:1.0.4' + api libs.j.ogg.vorbis } diff --git a/jme3-lwjgl/build.gradle b/jme3-lwjgl/build.gradle index afb8d91c77..b0f5a08d3b 100644 --- a/jme3-lwjgl/build.gradle +++ b/jme3-lwjgl/build.gradle @@ -2,12 +2,12 @@ dependencies { api project(':jme3-core') api project(':jme3-desktop') - api 'org.jmonkeyengine:lwjgl:2.9.5' + api libs.lwjgl2 /* * Upgrades the default jinput-2.0.5 to jinput-2.0.9 to fix a bug with gamepads on Linux. * See https://hub.jmonkeyengine.org/t/linux-gamepad-input-on-jme3-lwjgl-splits-input-between-two-logical-gamepads */ - api 'net.java.jinput:jinput:2.0.9' - api 'net.java.jinput:jinput:2.0.9:natives-all' + api libs.jinput + api(variantOf(libs.jinput){ classifier('natives-all') }) } \ No newline at end of file diff --git a/jme3-lwjgl3/build.gradle b/jme3-lwjgl3/build.gradle index e20ce923b9..4221519f9d 100644 --- a/jme3-lwjgl3/build.gradle +++ b/jme3-lwjgl3/build.gradle @@ -1,60 +1,55 @@ dependencies { api project(':jme3-core') api project(':jme3-desktop') - - api "org.lwjglx:lwjgl3-awt:0.1.8" - - api "org.lwjgl:lwjgl:${lwjgl3Version}" - api "org.lwjgl:lwjgl-glfw:${lwjgl3Version}" - api "org.lwjgl:lwjgl-jawt:${lwjgl3Version}" - api "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}" - api "org.lwjgl:lwjgl-openal:${lwjgl3Version}" - api "org.lwjgl:lwjgl-opencl:${lwjgl3Version}" - api "org.lwjgl:lwjgl-opengl:${lwjgl3Version}" - - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl:${lwjgl3Version}:natives-macos-arm64" - - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-glfw:${lwjgl3Version}:natives-macos-arm64" - - - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}:natives-macos-arm64" - - - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-opengl:${lwjgl3Version}:natives-macos-arm64" - - - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-windows-x86" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-linux-arm32" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-linux-arm64" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-openal:${lwjgl3Version}:natives-macos-arm64" - + api libs.lwjgl3.awt + + api libs.lwjgl3.base + api libs.lwjgl3.glfw + api libs.lwjgl3.jawt + api libs.lwjgl3.jemalloc + api libs.lwjgl3.openal + api libs.lwjgl3.opencl + api libs.lwjgl3.opengl + + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-macos-arm64') }) } javadoc { diff --git a/jme3-niftygui/build.gradle b/jme3-niftygui/build.gradle index 088cb562ef..b11c3e5017 100644 --- a/jme3-niftygui/build.gradle +++ b/jme3-niftygui/build.gradle @@ -1,6 +1,6 @@ dependencies { api project(':jme3-core') - api "com.github.nifty-gui:nifty:${niftyVersion}" - api "com.github.nifty-gui:nifty-default-controls:${niftyVersion}" - runtimeOnly "com.github.nifty-gui:nifty-style-black:${niftyVersion}" + api libs.nifty + api libs.nifty.default.controls + runtimeOnly libs.nifty.style.black } diff --git a/jme3-plugins-json-gson/build.gradle b/jme3-plugins-json-gson/build.gradle index 6d22f7a774..1acc4d3830 100644 --- a/jme3-plugins-json-gson/build.gradle +++ b/jme3-plugins-json-gson/build.gradle @@ -11,6 +11,6 @@ sourceSets { dependencies { api project(':jme3-plugins-json') - api 'com.google.code.gson:gson:2.9.1' + api libs.gson } diff --git a/jme3-vr/build.gradle b/jme3-vr/build.gradle index 07e3330c9c..6bcb4e766a 100644 --- a/jme3-vr/build.gradle +++ b/jme3-vr/build.gradle @@ -5,20 +5,20 @@ dependencies { api project(':jme3-effects') // https://mvnrepository.com/artifact/net.java.dev.jna/jna - implementation 'net.java.dev.jna:jna:5.10.0' - implementation 'com.nativelibs4java:jnaerator-runtime:0.12' + implementation libs.jna + implementation libs.jnaerator.runtime // Native LibOVR/Oculus support - api "org.lwjgl:lwjgl-ovr:${lwjgl3Version}" - runtimeOnly "org.lwjgl:lwjgl-ovr:${lwjgl3Version}:natives-windows" + api libs.lwjgl3.ovr + runtimeOnly(variantOf(libs.lwjgl3.ovr){ classifier('natives-windows') }) // Native OpenVR/LWJGL support - api "org.lwjgl:lwjgl-openvr:${lwjgl3Version}" - implementation "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-linux" - implementation "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-macos" - runtimeOnly "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-windows" - runtimeOnly "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-linux" - runtimeOnly "org.lwjgl:lwjgl-openvr:${lwjgl3Version}:natives-macos" + api libs.lwjgl3.openvr + implementation(variantOf(libs.lwjgl3.openvr){ classifier('natives-linux') }) + implementation(variantOf(libs.lwjgl3.openvr){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.openvr){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.openvr){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.openvr){ classifier('natives-macos') }) } javadoc { diff --git a/version.gradle b/version.gradle index 637b5ff72a..fbed055bf9 100644 --- a/version.gradle +++ b/version.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.ajoberstar:gradle-git:1.2.0' + classpath libs.gradle.git } } From 327426da517125d96d534c51842759acc354fd7a Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:21:41 -0400 Subject: [PATCH 50/57] Adds Custom Render Pipeline Interface (#2304) * added AlertArmatureMask * fixed docs and added another helper * made it easier to turn off priority checking * removed copy methods * jme3-core: 1. Added basic framework of framegraph to manage and organize new rendering passes; 2. Split existing rendering into several core RenderPasses; 3. Added multiple rendering paths (forward, deferred, tile based deferred); 4. Activated Framegraph system by useFramegraph, activated rendering paths by setRenderPath; * jme3-examples:Added a set of test cases for renderPath. * jme3-core:Improve deferred shading issues; Fix flickering issues in tile based deferred shading; * jme3-examples:Adjust deferred rendering test code * jme3-core:Adjust GBuffer format, RT0(16F), RT1(16F), RT2(16F), RT3(32F), DEPTH * jme3-core:Delete test code * jme3-core:Fixed all known bugs in Tile-based DeferredShading. * jme3-examples:update TestTileBasedDeferredShading * jme3-core:update GLSLCompat.glsllib * jme3-core:Fix frameGraph assertion error * jme3-core:Add some core code comments, delete Chinese code comments * jme3-core:Fix bugs existed in shadingModel * jme3-examples:Add test case "TestShadingModel.java,TestRenderPathPointDirectionalAndSpotLightShadows.java" * jme3-core:GBuffer data packing for terrain rendering compatibility (unlit, lighting) * jme3-examples: Added two renderPath sample codes. Added terrain unlit shading model test code; Added terrain lighting shading model test code; * jme3-core:Add rendering path support for AdvancedPBRTerrain * jme3-examples:Add a test code * jme3-core:Supplement core code comments, add full JME license to each java file, fix a few minor issues * jme3-terrain:Implement PBRTerrain compatibility code * jme3-core:Fixed Tile-basedDeferred light culling issue, fixed some flickering problems, fixed attenuation issue with multiple PBR lights under deferred rendering. * jme3-examples:update TestCode * jme3-examples:update TestPBRTerrainRenderPath.java * jme3-core:Fix TestTexture3D/TestTexture3DLoading * jme3-core:fix lightTexSizeInv undefined * jme3-core: 1.change java.com.jme3.renderer.renderPass=>java.com.jme3.renderer.pass 2.change IRenderGeometry.java=>RenderGeometry.java * jme3-core:use g_ViewProjectionMatrixInverse * jme3-core:use logger * jme3-core:update javadoc(FastMath.nextRandomFlot) * updated license * updated javadoc * renamed mask * refactor FGPass, FGSink, and FGSource to interfaces * fix method name collision for FGPass * renamed FGPass reset method * changes * rebuilt API * fixed framebuffer attachments bug * deferred shading usable * deferred and tiled deferred are working for TestShadingModel.java * debugging * debugging deferred pipeline * more debugging * more debugging * cleanup code * more cleanup * remove reliance on J3mLoader and clean up code * reorganized java files * added support for cross-space parameter binding * added optimizations * recoding resource system * redesigned resource system * framegraph operational * framegraph complete * draft ready * rename registration method * renamed method * delete personal changelog * remove enums * added information management passes * migrated to new ticket protocol * added resource extraction * provide logical control of framegraph * finalized user value sources and targets * fixed framegraph use on multiple viewports binding textures too often * fixed depth texture filtering * added export and import methods * javadoc for new classes * licenses * pass tests * added framegraph opt-out for viewports * added .j3g * delete unused classes * fixed background on deferred pipelines * added basic opencl * fixed tests * verified fix for overlapping viewports * added framegraph loading speed test * added framegraph event capture * fixed framebuffer update flag * fixed broken light count * revert debugging * fix merge conflict * start moving lighting logic outside matdef logic * added ticket groups * migrated lighting logic to render pass * fixed light tex inverse type * added light pack method junction * deleted deferred lighting logic * deleted draft files * added group attribute pass * added indefinite ticket groups * finalized ticket lists * porting filters * narrowed PBR issue to metallic=1 * fixed functional array issues * fixed build errors * fixed depth of field filter import-export * fixed deferred light probes * fixed ambient and fixed buffered lights * fixed gbuffer packing for advanced terrain * fixed pbr terrain * made render object map threadsafe * added multithreading * fix referencing bug * added fix for missing defines * test async * fix dependency on java 9 * removed unnecessary atomic boolean * fixed more async issues * fixed camera size when rendering to smaller textures * added addLoop * add faze pass * improved javadoc and fixed issue with deferred techniquedefs * added pipeline interface and added render modules * changes to factory methods * deleted framegraph system * removed gbuffer shaders * restored deferred and gbuf shaders * removed deferred materials * removed effects passes * deleted tests * reverted unnecessary changes and moved pipeline classes to new package * reverted more unnecessary changes * small changes * several changes * revert changes in build file * removed material adaptation code * removed savable utilities * removed math utility * made render context implementation less confusing * pass viewport to pipeline context * fixed start method not being called * remove miscellaneous * removed miscellaneous * remove miscellaneous * remove miscellaneous * changed render start order * added license and attempted to revert changes * remove target creation helpers * added spacing * several small changes * added null pipeline and context creation helper * delete GeometryRenderHandler and RenderUtils --------- Co-authored-by: JohnKkk <18402012144@163.com> Co-authored-by: chenliming --- build.gradle | 2 +- .../java/com/jme3/material/TechniqueDef.java | 2 +- .../java/com/jme3/renderer/RenderManager.java | 380 +++++++++++------- .../main/java/com/jme3/renderer/ViewPort.java | 30 +- .../pipeline/DefaultPipelineContext.java | 62 +++ .../renderer/pipeline/ForwardPipeline.java | 159 ++++++++ .../jme3/renderer/pipeline/NullPipeline.java | 42 ++ .../renderer/pipeline/PipelineContext.java | 70 ++++ .../renderer/pipeline/RenderPipeline.java | 88 ++++ .../com/jme3/renderer/queue/RenderQueue.java | 16 +- .../java/com/jme3/texture/FrameBuffer.java | 35 +- .../Common/MatDefs/Light/Deferred.j3md | 2 +- .../Common/MatDefs/Light/Deferred.vert | 2 +- .../resources/Common/MatDefs/Light/GBuf.vert | 2 +- .../Common/ShaderLib/Instancing.glsllib | 2 +- .../com/jme3/material/plugins/J3MLoader.java | 2 +- .../jme3/post/filters/DepthOfFieldFilter.java | 2 +- .../Common/MatDefs/Post/CartoonEdge.frag | 2 +- .../Common/MatDefs/Post/CrossHatch.frag | 2 +- .../resources/Common/MatDefs/Post/FXAA.frag | 2 +- jme3-examples/gradle.properties | 2 +- .../MatDefs/Terrain/HeightBasedTerrain.j3md | 2 +- .../Common/MatDefs/Terrain/Terrain.j3md | 4 +- 23 files changed, 736 insertions(+), 176 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java create mode 100644 jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java create mode 100644 jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java create mode 100644 jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java create mode 100644 jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java diff --git a/build.gradle b/build.gradle index fbcc6f54a6..b74ab32145 100644 --- a/build.gradle +++ b/build.gradle @@ -250,4 +250,4 @@ retrolambda { javaVersion JavaVersion.VERSION_1_7 incremental true jvmArgs '-noverify' -} \ No newline at end of file +} 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 b620c9ceaf..6e40ff366e 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -824,4 +824,4 @@ public TechniqueDef clone() throws CloneNotSupportedException { return clone; } -} +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index df1c42f442..c59ffd9084 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,10 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.ForwardPipeline; +import com.jme3.renderer.pipeline.DefaultPipelineContext; +import com.jme3.renderer.pipeline.RenderPipeline; +import com.jme3.renderer.pipeline.PipelineContext; import com.jme3.light.DefaultLightFilter; import com.jme3.light.LightFilter; import com.jme3.light.LightList; @@ -44,7 +48,6 @@ import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; -import com.jme3.profile.SpStep; import com.jme3.profile.VpStep; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; @@ -65,8 +68,12 @@ import com.jme3.util.SafeArrayList; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.logging.Logger; /** @@ -87,6 +94,10 @@ public class RenderManager { private final ArrayList preViewPorts = new ArrayList<>(); private final ArrayList viewPorts = new ArrayList<>(); private final ArrayList postViewPorts = new ArrayList<>(); + private final HashMap contexts = new HashMap<>(); + private final LinkedList usedContexts = new LinkedList<>(); + private final LinkedList usedPipelines = new LinkedList<>(); + private RenderPipeline defaultPipeline = new ForwardPipeline(); private Camera prevCam = null; private Material forcedMaterial = null; private String forcedTechnique = null; @@ -104,7 +115,7 @@ public class RenderManager { private LightFilter lightFilter = new DefaultLightFilter(); private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass; private int singlePassLightBatchSize = 1; - private MatParamOverride boundDrawBufferId=new MatParamOverride(VarType.Int,"BoundDrawBuffer",0); + private MatParamOverride boundDrawBufferId=new MatParamOverride(VarType.Int, "BoundDrawBuffer", 0); private Predicate renderFilter; @@ -117,6 +128,115 @@ public class RenderManager { public RenderManager(Renderer renderer) { this.renderer = renderer; this.forcedOverrides.add(boundDrawBufferId); + // register default pipeline context + contexts.put(PipelineContext.class, new DefaultPipelineContext()); + } + + /** + * Gets the default pipeline used when a ViewPort does not have a + * pipeline already assigned to it. + * + * @return + */ + public RenderPipeline getPipeline() { + return defaultPipeline; + } + + /** + * Sets the default pipeline used when a ViewPort does not have a + * pipeline already assigned to it. + *

+ * default={@link ForwardPipeline} + * + * @param pipeline default pipeline (not null) + */ + public void setPipeline(RenderPipeline pipeline) { + assert pipeline != null; + this.defaultPipeline = pipeline; + } + + /** + * Gets the default pipeline context registered under + * {@link PipelineContext#getClass()}. + * + * @return + */ + public PipelineContext getDefaultContext() { + return getContext(PipelineContext.class); + } + + /** + * Gets the pipeline context registered under the class. + * + * @param + * @param type + * @return registered context or null + */ + public T getContext(Class type) { + return (T)contexts.get(type); + } + + /** + * Gets the pipeline context registered under the class or creates + * and registers a new context from the supplier. + * + * @param + * @param type + * @param supplier interface for creating a new context if necessary + * @return registered or newly created context + */ + public T getOrCreateContext(Class type, Supplier supplier) { + T c = getContext(type); + if (c == null) { + c = supplier.get(); + registerContext(type, c); + } + return c; + } + + /** + * Gets the pipeline context registered under the class or creates + * and registers a new context from the function. + * + * @param + * @param type + * @param function interface for creating a new context if necessary + * @return registered or newly created context + */ + public T getOrCreateContext(Class type, Function function) { + T c = getContext(type); + if (c == null) { + c = function.apply(this); + registerContext(type, c); + } + return c; + } + + /** + * Registers the pipeline context under the class. + *

+ * If another context is already registered under the class, that + * context will be replaced by the given context. + * + * @param + * @param type class type to register the context under (not null) + * @param context context to register (not null) + */ + public void registerContext(Class type, T context) { + assert type != null; + if (context == null) { + throw new NullPointerException("Context to register cannot be null."); + } + contexts.put(type, context); + } + + /** + * Gets the application profiler. + * + * @return + */ + public AppProfiler getProfiler() { + return prof; } /** @@ -402,7 +522,7 @@ public void notifyRescale(float x, float y) { for (ViewPort vp : preViewPorts) { notifyRescale(vp, x, y); } - for (ViewPort vp : viewPorts) { + for (ViewPort vp : viewPorts) { notifyRescale(vp, x, y); } for (ViewPort vp : postViewPorts) { @@ -422,6 +542,15 @@ public void notifyRescale(float x, float y) { public void setForcedMaterial(Material mat) { forcedMaterial = mat; } + + /** + * Gets the forced material. + * + * @return + */ + public Material getForcedMaterial() { + return forcedMaterial; + } /** * Returns the forced render state previously set with @@ -628,7 +757,33 @@ public void updateUniformBindings(Shader shader) { * @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) */ public void renderGeometry(Geometry geom) { - if (renderFilter != null && !renderFilter.test(geom)) return; + + if (renderFilter != null && !renderFilter.test(geom)) { + return; + } + + LightList lightList = geom.getWorldLightList(); + if (lightFilter != null) { + filteredLightList.clear(); + lightFilter.filterLights(geom, filteredLightList); + lightList = filteredLightList; + } + + renderGeometry(geom, lightList); + + } + + /** + * + * @param geom + * @param lightList + */ + public void renderGeometry(Geometry geom, LightList lightList) { + + if (renderFilter != null && !renderFilter.test(geom)) { + return; + } + this.renderer.pushDebugGroup(geom.getName()); if (geom.isIgnoreTransform()) { setWorldMatrix(Matrix4f.IDENTITY); @@ -636,23 +791,15 @@ public void renderGeometry(Geometry geom) { setWorldMatrix(geom.getWorldMatrix()); } - // Use material override to pass the current target index (used in api such as GL ES that do not support glDrawBuffer) + // Use material override to pass the current target index (used in api such as GL ES + // that do not support glDrawBuffer) FrameBuffer currentFb = this.renderer.getCurrentFrameBuffer(); if (currentFb != null && !currentFb.isMultiTarget()) { this.boundDrawBufferId.setValue(currentFb.getTargetIndex()); } - // Perform light filtering if we have a light filter. - LightList lightList = geom.getWorldLightList(); - - if (lightFilter != null) { - filteredLightList.clear(); - lightFilter.filterLights(geom, filteredLightList); - lightList = filteredLightList; - } - Material material = geom.getMaterial(); - + // If forcedTechnique exists, we try to force it for the render. // If it does not exist in the mat def, we check for forcedMaterial and render the geom if not null. // Otherwise, the geometry is not rendered. @@ -736,7 +883,7 @@ public void preloadScene(Spatial scene) { preloadScene(children.get(i)); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); @@ -755,7 +902,7 @@ public void preloadScene(Spatial scene) { } } } - + /** * Flattens the given scene graph into the ViewPort's RenderQueue, * checking for culling as the call goes down the graph recursively. @@ -786,10 +933,10 @@ public void preloadScene(Spatial scene) { * contain the flattened scene graph. */ public void renderScene(Spatial scene, ViewPort vp) { - //reset of the camera plane state for proper culling - //(must be 0 for the first note of the scene to be rendered) + // reset of the camera plane state for proper culling + // (must be 0 for the first note of the scene to be rendered) vp.getCamera().setPlaneState(0); - //rendering the scene + // queue the scene for rendering renderSubScene(scene, vp); } @@ -800,12 +947,10 @@ public void renderScene(Spatial scene, ViewPort vp) { * @param vp the ViewPort to render in (not null) */ private void renderSubScene(Spatial scene, ViewPort vp) { - - // check culling first. + // check culling first if (!scene.checkCulling(vp.getCamera())) { return; } - scene.runControlRender(this, vp); if (scene instanceof Node) { // Recurse for all children @@ -819,12 +964,11 @@ private void renderSubScene(Spatial scene, ViewPort vp) { renderSubScene(children.get(i), vp); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); } - vp.getQueue().addToQueue(gm, scene.getQueueBucket()); } } @@ -1038,7 +1182,7 @@ public void renderTranslucentQueue(ViewPort vp) { } private void setViewPort(Camera cam) { - // this will make sure to update viewport only if needed + // this will make sure to clearReservations viewport only if needed if (cam != prevCam || cam.isViewportChanged()) { viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); @@ -1120,141 +1264,54 @@ public void renderViewPortRaw(ViewPort vp) { } /** - * Renders the {@link ViewPort}. - * - *

If the ViewPort is {@link ViewPort#isEnabled() disabled}, this method - * returns immediately. Otherwise, the ViewPort is rendered by - * the following process:
- *

    - *
  • All {@link SceneProcessor scene processors} that are attached - * to the ViewPort are {@link SceneProcessor#initialize(com.jme3.renderer.RenderManager, - * com.jme3.renderer.ViewPort) initialized}. - *
  • - *
  • The SceneProcessors' {@link SceneProcessor#preFrame(float) } method - * is called.
  • - *
  • The ViewPort's {@link ViewPort#getOutputFrameBuffer() output framebuffer} - * is set on the Renderer
  • - *
  • The camera is set on the renderer, including its view port parameters. - * (see {@link #setCamera(com.jme3.renderer.Camera, boolean) })
  • - *
  • Any buffers that the ViewPort requests to be cleared are cleared - * and the {@link ViewPort#getBackgroundColor() background color} is set
  • - *
  • Every scene that is attached to the ViewPort is flattened into - * the ViewPort's render queue - * (see {@link #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) }) - *
  • - *
  • The SceneProcessors' {@link SceneProcessor#postQueue(com.jme3.renderer.queue.RenderQueue) } - * method is called.
  • - *
  • The render queue is sorted and then flushed, sending - * rendering commands to the underlying Renderer implementation. - * (see {@link #flushQueue(com.jme3.renderer.ViewPort) })
  • - *
  • The SceneProcessors' {@link SceneProcessor#postFrame(com.jme3.texture.FrameBuffer) } - * method is called.
  • - *
  • The translucent queue of the ViewPort is sorted and then flushed - * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })
  • - *
  • If any objects remained in the render queue, they are removed - * from the queue. This is generally objects added to the - * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, - * com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) - * shadow queue} - * which were not rendered because of a missing shadow renderer.
  • - *
- * - * @param vp View port to render - * @param tpf Time per frame value + * Applies the ViewPort's Camera and FrameBuffer in preparation + * for rendering. + * + * @param vp */ - public void renderViewPort(ViewPort vp, float tpf) { - if (!vp.isEnabled()) { - return; - } - if (prof != null) { - prof.vpStep(VpStep.BeginRender, vp, null); - } - - SafeArrayList processors = vp.getProcessors(); - if (processors.isEmpty()) { - processors = null; - } - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PreFrame, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (!proc.isInitialized()) { - proc.initialize(this, vp); - } - proc.setProfiler(this.prof); - if (prof != null) { - prof.spStep(SpStep.ProcPreFrame, proc.getClass().getSimpleName()); - } - proc.preFrame(tpf); - } - } - + public void applyViewPort(ViewPort vp) { renderer.setFrameBuffer(vp.getOutputFrameBuffer()); setCamera(vp.getCamera(), false); if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { if (vp.isClearColor()) { renderer.setBackgroundColor(vp.getBackgroundColor()); } - renderer.clearBuffers(vp.isClearColor(), - vp.isClearDepth(), - vp.isClearStencil()); - } - - if (prof != null) { - prof.vpStep(VpStep.RenderScene, vp, null); + renderer.clearBuffers(vp.isClearColor(), vp.isClearDepth(), vp.isClearStencil()); } - List scenes = vp.getScenes(); - for (int i = scenes.size() - 1; i >= 0; i--) { - renderScene(scenes.get(i), vp); - } - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PostQueue, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) { - prof.spStep(SpStep.ProcPostQueue, proc.getClass().getSimpleName()); - } - proc.postQueue(vp.getQueue()); - } + } + + /** + * Renders the {@link ViewPort} using the ViewPort's {@link RenderPipeline}. + *

+ * If the ViewPort's RenderPipeline is null, the pipeline returned by + * {@link #getPipeline()} is used instead. + *

+ * If the ViewPort is disabled, no rendering will occur. + * + * @param vp View port to render + * @param tpf Time per frame value + */ + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; + } + RenderPipeline pipeline = vp.getPipeline(); + if (pipeline == null) { + pipeline = defaultPipeline; } - - if (prof != null) { - prof.vpStep(VpStep.FlushQueue, vp, null); + PipelineContext context = pipeline.fetchPipelineContext(this); + if (context == null) { + throw new NullPointerException("Failed to fetch pipeline context."); } - flushQueue(vp); - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PostFrame, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) { - prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); - } - proc.postFrame(vp.getOutputFrameBuffer()); - } - if (prof != null) { - prof.vpStep(VpStep.ProcEndRender, vp, null); - } + if (!context.startViewPortRender(this, vp)) { + usedContexts.add(context); } - //renders the translucent objects queue after processors have been rendered - renderTranslucentQueue(vp); - // clear any remaining spatials that were not rendered. - clearQueue(vp); - - /* - * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results - * if renderer#copyFrameBuffer is used later - */ - renderer.clearClipRect(); - - if (prof != null) { - prof.vpStep(VpStep.EndRender, vp, null); + if (!pipeline.hasRenderedThisFrame()) { + usedPipelines.add(pipeline); + pipeline.startRenderFrame(this); } + pipeline.pipelineRender(this, context, vp, tpf); + context.endViewPortRender(this, vp); } /** @@ -1276,7 +1333,7 @@ public void render(float tpf, boolean mainFrameBufferActive) { if (renderer instanceof NullRenderer) { return; } - + uniformBindingManager.newFrame(); if (prof != null) { @@ -1308,12 +1365,22 @@ public void render(float tpf, boolean mainFrameBufferActive) { renderViewPort(vp, tpf); } } + + // cleanup for used render pipelines and pipeline contexts only + for (PipelineContext c : usedContexts) { + c.endContextRenderFrame(this); + } + for (RenderPipeline p : usedPipelines) { + p.endRenderFrame(this); + } + usedContexts.clear(); + usedPipelines.clear(); + } - /** * Returns true if the draw buffer target id is passed to the shader. - * + * * @return True if the draw buffer target id is passed to the shaders. */ public boolean getPassDrawBufferTargetIdToShaders() { @@ -1324,7 +1391,7 @@ public boolean getPassDrawBufferTargetIdToShaders() { * Enable or disable passing the draw buffer target id to the shaders. This * is needed to handle FrameBuffer.setTargetIndex correctly in some * backends. - * + * * @param v * True to enable, false to disable (default is true) */ @@ -1337,11 +1404,12 @@ public void setPassDrawBufferTargetIdToShaders(boolean v) { this.forcedOverrides.remove(boundDrawBufferId); } } + /** * Set a render filter. Every geometry will be tested against this filter * before rendering and will only be rendered if the filter returns true. * - * @param filter + * @param filter the render filter */ public void setRenderFilter(Predicate filter) { renderFilter = filter; @@ -1350,7 +1418,7 @@ public void setRenderFilter(Predicate filter) { /** * Returns the render filter that the RenderManager is currently using * - * @return the render filter + * @return the render filter */ public Predicate getRenderFilter() { return renderFilter; diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java index 6f0424fb30..f256405276 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java +++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.RenderPipeline; import com.jme3.math.ColorRGBA; import com.jme3.post.SceneProcessor; import com.jme3.renderer.queue.RenderQueue; @@ -84,6 +85,10 @@ public class ViewPort { * Scene processors currently applied. */ protected final SafeArrayList processors = new SafeArrayList<>(SceneProcessor.class); + /** + * Dedicated pipeline. + */ + protected RenderPipeline pipeline; /** * FrameBuffer for output. */ @@ -424,5 +429,28 @@ public void setEnabled(boolean enable) { public boolean isEnabled() { return enabled; } + + /** + * Sets the pipeline used by this viewport for rendering. + *

+ * If null, the render manager's default pipeline will be used + * to render this viewport. + *

+ * default=null + * + * @param pipeline pipeline, or null to use render manager's pipeline + */ + public void setPipeline(RenderPipeline pipeline) { + this.pipeline = pipeline; + } + + /** + * Gets the framegraph used by this viewport for rendering. + * + * @return + */ + public RenderPipeline getPipeline() { + return pipeline; + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java new file mode 100644 index 0000000000..d4af168a6d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Default implementation of AbstractPipelineContext that + * does nothing extra. + * + * @author codex + */ +public class DefaultPipelineContext implements PipelineContext { + + // decided to use an atomic boolean, since it is less hassle + private final AtomicBoolean rendered = new AtomicBoolean(false); + + @Override + public boolean startViewPortRender(RenderManager rm, ViewPort vp) { + return rendered.getAndSet(true); + } + + @Override + public void endViewPortRender(RenderManager rm, ViewPort vp) {} + + @Override + public void endContextRenderFrame(RenderManager rm) { + rendered.set(false); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java new file mode 100644 index 0000000000..2f02ab3e82 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 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.pipeline; + +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.SpStep; +import com.jme3.profile.VpStep; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.util.SafeArrayList; +import java.util.List; + +/** + * Port of the standard forward renderer to a pipeline. + * + * @author codex + */ +public class ForwardPipeline implements RenderPipeline { + + private boolean rendered = false; + + @Override + public PipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getDefaultContext(); + } + + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + + @Override + public void startRenderFrame(RenderManager rm) {} + + @Override + public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) { + + AppProfiler prof = rm.getProfiler(); + + SafeArrayList processors = vp.getProcessors(); + if (processors.isEmpty()) { + processors = null; + } + + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PreFrame, vp, null); + } + for (SceneProcessor p : processors.getArray()) { + if (!p.isInitialized()) { + p.initialize(rm, vp); + } + p.setProfiler(prof); + if (prof != null) { + prof.spStep(SpStep.ProcPreFrame, p.getClass().getSimpleName()); + } + p.preFrame(tpf); + } + } + + rm.applyViewPort(vp); + + if (prof != null) { + prof.vpStep(VpStep.RenderScene, vp, null); + } + // flatten scenes into render queue + List scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + rm.renderScene(scenes.get(i), vp); + } + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PostQueue, vp, null); + } + for (SceneProcessor p : processors.getArray()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostQueue, p.getClass().getSimpleName()); + } + p.postQueue(vp.getQueue()); + } + } + + if (prof != null) { + prof.vpStep(VpStep.FlushQueue, vp, null); + } + rm.flushQueue(vp); + + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PostFrame, vp, null); + } + for (SceneProcessor proc : processors.getArray()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); + } + proc.postFrame(vp.getOutputFrameBuffer()); + } + if (prof != null) { + prof.vpStep(VpStep.ProcEndRender, vp, null); + } + } + + // render the translucent objects queue after processors have been rendered + rm.renderTranslucentQueue(vp); + + // clear any remaining spatials that were not rendered. + rm.clearQueue(vp); + + rendered = true; + + /* + * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results + * if renderer#copyFrameBuffer is used later + */ + rm.getRenderer().clearClipRect(); + + if (prof != null) { + prof.vpStep(VpStep.EndRender, vp, null); + } + + } + + @Override + public void endRenderFrame(RenderManager rm) { + rendered = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java new file mode 100644 index 0000000000..b104e5bd5d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java @@ -0,0 +1,42 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Render pipeline that performs no rendering. + * + * @author codex + */ +public class NullPipeline implements RenderPipeline { + + private boolean rendered = false; + + @Override + public PipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getDefaultContext(); + } + + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + + @Override + public void startRenderFrame(RenderManager rm) {} + + @Override + public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) { + rendered = true; + } + + @Override + public void endRenderFrame(RenderManager rm) { + rendered = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java new file mode 100644 index 0000000000..32a4fa2e2d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Handles objects globally for a single type of RenderPipeline. + * + * @author codex + */ +public interface PipelineContext { + + /** + * Called when a ViewPort rendering session starts that this context + * is participating in. + * + * @param rm + * @param vp viewport being rendered + * @return true if this context has already rendered a viewport this frame + */ + public boolean startViewPortRender(RenderManager rm, ViewPort vp); + + /** + * Called when viewport rendering session ends that this context + * is participating in. + * + * @param rm + * @param vp viewport being rendered + */ + public void endViewPortRender(RenderManager rm, ViewPort vp); + + /** + * Called at the end of a render frame this context participated in. + * + * @param rm + */ + public void endContextRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java new file mode 100644 index 0000000000..44f76d6e86 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 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.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Pipeline for rendering a ViewPort. + * + * @author codex + * @param + */ +public interface RenderPipeline { + + /** + * Fetches the PipelineContext this pipeline requires for rendering + * from the RenderManager. + * + * @param rm + * @return pipeline context (not null) + */ + public T fetchPipelineContext(RenderManager rm); + + /** + * Returns true if this pipeline has rendered a viewport this render frame. + * + * @return + */ + public boolean hasRenderedThisFrame(); + + /** + * Called before this pipeline is rendered for the first time this frame. + *

+ * Only called if the pipeline will actually be rendered. + * + * @param rm + */ + public void startRenderFrame(RenderManager rm); + + /** + * Renders the pipeline. + * + * @param rm + * @param context + * @param vp + * @param tpf + */ + public void pipelineRender(RenderManager rm, T context, ViewPort vp, float tpf); + + /** + * Called after all rendering is complete in a rendering frame this + * pipeline participated in. + * + * @param rm + */ + public void endRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java index 86c5bc5280..a16dc18665 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java @@ -264,7 +264,7 @@ public void addToQueue(Geometry g, Bucket bucket) { } } - private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean flush) { list.setCamera(cam); // select camera for sorting list.sort(); for (int i = 0; i < list.size(); i++) { @@ -273,7 +273,7 @@ private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, rm.renderGeometry(obj); obj.queueDistance = Float.NEGATIVE_INFINITY; } - if (clear) { + if (flush) { list.clear(); } } @@ -329,6 +329,18 @@ public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean cle } rm.getRenderer().popDebugGroup(); } + + public GeometryList getList(Bucket bucket) { + switch (bucket) { + case Opaque: return opaqueList; + case Gui: return guiList; + case Transparent: return transparentList; + case Translucent: return translucentList; + case Sky: return skyList; + default: + throw new UnsupportedOperationException(); + } + } public void clear() { opaqueList.clear(); 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 573652e4cc..f3cc721df9 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -179,7 +179,6 @@ public int getLayer() { return this.layer; } } - public static class FrameBufferTextureTarget extends RenderBuffer { private FrameBufferTextureTarget(){} @@ -260,12 +259,44 @@ public void addColorTarget(FrameBufferBufferTarget colorBuf){ colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } - + public void addColorTarget(FrameBufferTextureTarget colorBuf){ // checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels. colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } + + /** + * Replaces the color target at the index. + *

+ * A color target must already exist at the index, otherwise + * an exception will be thrown. + * + * @param i index of color target to replace + * @param colorBuf color target to replace with + */ + public void replaceColorTarget(int i, FrameBufferTextureTarget colorBuf) { + if (i < 0 || i >= colorBufs.size()) { + throw new IndexOutOfBoundsException("No color target exists to replace at index=" + i); + } + colorBuf.slot = i; + colorBufs.set(i, colorBuf); + } + + /** + * Removes the color target at the index. + *

+ * Color targets above the removed target will have their + * slot indices shifted accordingly. + * + * @param i + */ + public void removeColorTarget(int i) { + colorBufs.remove(i); + for (; i < colorBufs.size(); i++) { + colorBufs.get(i).slot = i; + } + } /** * Adds a texture to one of the color Buffers Array. It uses {@link TextureCubeMap} ordinal number for the diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md index 08a7d007f8..7586e854fb 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md @@ -61,4 +61,4 @@ MaterialDef Phong Lighting Deferred { Technique { } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert index 1d22a36edb..e247694a98 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert @@ -9,4 +9,4 @@ void main(){ texCoord = inTexCoord; vec4 pos = vec4(inPosition, 1.0); gl_Position = vec4(sign(pos.xy-vec2(0.5)), 0.0, 1.0); -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert index 66eca303b0..d72712854f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert @@ -70,4 +70,4 @@ void main(){ #ifdef VERTEX_COLOR DiffuseSum *= inColor; #endif -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 37c3a40cf2..bffb200084 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -106,4 +106,4 @@ vec3 TransformWorldNormal(vec3 normal) { } -#endif \ No newline at end of file +#endif diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 5c82c91b6f..e874ed555c 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 @@ -968,4 +968,4 @@ public void applyToTexture(final Texture texture) { textureOption.applyToTexture(value, texture); } } -} +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java index e8f5029c37..6174332699 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java @@ -243,4 +243,4 @@ public void read(JmeImporter im) throws IOException { focusRange = ic.readFloat("focusRange", 10f); debugUnfocus = ic.readBoolean("debugUnfocus", false); } -} +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag index 7ee2e51d4a..219459931b 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag @@ -55,4 +55,4 @@ void main(){ color = mix (color,m_EdgeColor.rgb,edgeAmount); gl_FragColor = vec4(color, 1.0); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag index fe52cfc502..99b31175e4 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag @@ -51,4 +51,4 @@ void main() { vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper); gl_FragColor = mix(paperColor, lineColor, linePixel); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag index eb03e8f565..edd29d88c5 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag @@ -86,4 +86,4 @@ void main() { vec4 texVal = texture2D(m_Texture, texCoord); gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, g_ResolutionInverse), texVal.a); -} \ No newline at end of file +} diff --git a/jme3-examples/gradle.properties b/jme3-examples/gradle.properties index 5b85c3b85b..bfcfb949b5 100644 --- a/jme3-examples/gradle.properties +++ b/jme3-examples/gradle.properties @@ -2,4 +2,4 @@ assertions = true # Build javadoc per Github issue #1366 -buildJavaDoc = true \ No newline at end of file +buildJavaDoc = true diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md index d2bfa0c01d..f84da24224 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md @@ -42,4 +42,4 @@ MaterialDef Terrain { Technique { } -} \ No newline at end of file +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md index 1ac3b21dc1..00bb60445d 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md @@ -18,7 +18,7 @@ MaterialDef Terrain { Technique { VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.vert FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.frag - + WorldParameters { WorldViewProjectionMatrix } @@ -31,4 +31,4 @@ MaterialDef Terrain { Technique { } -} \ No newline at end of file +} From a45ca4a2b49ace50ba6c5ea96e5493e172d0f97b Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 24 Oct 2024 09:49:13 -0700 Subject: [PATCH 51/57] remove unused submodule "jme-angle" (issue #2320) (#2321) --- jme-angle/src/native/angle | 1 - 1 file changed, 1 deletion(-) delete mode 160000 jme-angle/src/native/angle diff --git a/jme-angle/src/native/angle b/jme-angle/src/native/angle deleted file mode 160000 index 2319607679..0000000000 --- a/jme-angle/src/native/angle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2319607679d7781ff9bab5e821a34574ecb0bcc3 From e0953add9d48b234059b064b839ed69784300f9b Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 24 Oct 2024 09:58:48 -0700 Subject: [PATCH 52/57] update the groovy-test library to v3.0.22 (#2319) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 655e7e6a83..b21814aee0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ androidx-annotation = "androidx.annotation:annotation:1.3.0" androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.4.0" gradle-git = "org.ajoberstar:gradle-git:1.2.0" gradle-retrolambda = "me.tatarka:gradle-retrolambda:3.7.1" -groovy-test = "org.codehaus.groovy:groovy-test:3.0.21" +groovy-test = "org.codehaus.groovy:groovy-test:3.0.22" gson = "com.google.code.gson:gson:2.9.1" j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.4" jbullet = "com.github.stephengold:jbullet:1.0.3" From eee43564c4002675a3dbaed7743b755a62659175 Mon Sep 17 00:00:00 2001 From: JosiahGoeman <31492985+JosiahGoeman@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:30:06 -0400 Subject: [PATCH 53/57] Fix for XMLExporter issues in #2310 (#2313) * #2176 Make LWJGLBufferAllocator use nmemCalloc() instead of nmemAlloc() https://github.com/jMonkeyEngine/jmonkeyengine/issues/2176 LWJGLBufferAllocator.allocate() now always returns zero-initialized buffers. * Added unit tests for JmeExporter/JmeImporter implementations Tests all write* and read* methods of OutputCapsule and InputCapsule respectively. * Fixed XMLExporter/XMLImporter BitSets Previously DOMOutputCapsule was writing indices of set bits and DOMInputCapsule was reading values of each bit. Changed DOMOutputCapsule to match the expected behavior of DOMInputCapsule. * Fixed DOMInputCapsule.readString() returning defVal for empty strings org.w3c.dom.Element.getAttribute() returns an empty string for attributes that are not found. It looks like DOMInputCapsule.readString() was interpreting an empty string as the attribute not existing, and returning defVal even when the attribute did exist and was an empty string. Now it checks explicitly whether the attribute exists. * Deprecated DOMSerializer in favor of javax.xml.transform.Transformer DOMSerializer contains several edge-case issues that were only partially worked around with the encodeString() and decodeString() helper methods. Java has a robust built-in solution to serializing Document objects, and using that instead fixes several bugs. * Fixed NullPointerException when XMLExporter writes a String[] with null Also refactored all primitive array write and read methods to be more readable and reduce duplicate code. * Made DOM capsules reuse write/read primitive array methods for buffers Further reduces duplicate code * Fixed DOMOutputCapsule.write(Savable[][]) NullPointerException Refactored write and read methods for Savables and 1 and 2 dimensional Savable arrays. Fixed NullPointerException when writing a 2d Savable array containing a null element in the outer array. * Added Savable reference consistency test to InputOutputCapsuleTest * Fixed DOMInputCapsule throwing NullPointerException when reading list DOMInputCapsule used to throw a NullPointerException when reading an Arraylist containing a null element. Also refactored list write and read methods to clean up a bit and accidentally also fixed an unrelated bug where reading ArrayList would return a list containing all null elements. * Made XMLExporter save and load buffer positions properly. * Cleanup and formatting for XMLExporter related classes * Undid XMLExporter saving buffer positions. Not saving positions is intentional https://github.com/jMonkeyEngine/jmonkeyengine/issues/2312#issuecomment-2359149509 * Fixed infinite recursion with XMLExporter Writing a Savable containing a reference loop caused infinite recursion due to bookkeeping being performed after the recursive call instead of before. Also added a unit test for this to InputOutputCapsuleTest. --- .../jme3/export/InputOutputCapsuleTest.java | 717 +++++++ .../com/jme3/export/xml/DOMInputCapsule.java | 1724 ++++++----------- .../com/jme3/export/xml/DOMOutputCapsule.java | 946 ++++----- .../com/jme3/export/xml/DOMSerializer.java | 2 + .../java/com/jme3/export/xml/XMLExporter.java | 62 +- .../java/com/jme3/export/xml/XMLImporter.java | 4 +- .../java/com/jme3/export/xml/XMLUtils.java | 14 +- 7 files changed, 1751 insertions(+), 1718 deletions(-) create mode 100644 jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java diff --git a/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java b/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java new file mode 100644 index 0000000000..74edd66d5a --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2023 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.export; + +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.BitSet; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.File; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.nio.IntBuffer; +import java.nio.FloatBuffer; +import java.util.Iterator; + +import org.junit.Assert; +import org.junit.Test; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.AssetKey; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.export.xml.XMLExporter; +import com.jme3.export.xml.XMLImporter; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.math.Vector3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Matrix4f; + +/** + * Test suite for implementations of the JmeExporter and JmeImporter interfaces. + * There are tests here for all write* and read* methods of the OutputCapsule and InputCapsule interfaces respectively. + */ +public class InputOutputCapsuleTest { + private static final List exporters = new ArrayList<>(); + private static final List importers = new ArrayList<>(); + static { + exporters.add(new BinaryExporter()); + importers.add(new BinaryImporter()); + + exporters.add(new XMLExporter()); + importers.add(new XMLImporter()); + + // add any future implementations here + } + + @Test + public void testPrimitives() { + saveAndLoad(new TestPrimitives()); + } + + @Test + public void testStrings() { + saveAndLoad(new TestStrings()); + } + + @Test + public void testEnums() { + saveAndLoad(new TestEnums()); + } + + @Test + public void testBitSets() { + saveAndLoad(new TestBitSets()); + } + + @Test + public void testSavables() { + saveAndLoad(new TestSavables()); + } + + @Test + public void testSavableReferences() { + saveAndLoad(new TestSavableReferences()); + } + + @Test + public void testArrays() { + saveAndLoad(new TestArrays()); + } + + @Test + public void testBuffers() { + saveAndLoad(new TestBuffers()); + } + + @Test + public void testLists() { + saveAndLoad(new TestLists()); + } + + @Test + public void testMaps() { + saveAndLoad(new TestMaps()); + } + + // attempts to save and load a Savable using the JmeExporter/JmeImporter implementations listed at the top of this class. + // the Savable inner classes in this file run assertions in their read() methods + // to ensure the data loaded is the same as what was written. more or less stole this from JmeExporterTest.java + private static void saveAndLoad(Savable savable) { + for (int i = 0; i < exporters.size(); i++) { + JmeExporter exporter = exporters.get(i); + JmeImporter importer = importers.get(i); + + // export + byte[] exportedBytes = null; + try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) { + exporter.save(savable, outStream); + exportedBytes = outStream.toByteArray(); + } catch (IOException e) { + Assert.fail(exporter.getClass().getSimpleName() + ": " + e.toString()); + } + + // write the xml into files for debugging. + // leave this commented out unless you need it since it makes a mess of the jme3-plugins directory. + /*if (exporter instanceof XMLExporter) { + try { + File outFile = new File(savable.getClass().getSimpleName() + ".xml"); + outFile.createNewFile(); + PrintWriter out = new PrintWriter(outFile); + out.print(new String(exportedBytes)); + out.close(); + } catch(IOException ioEx) { + + } + }*/ + + // import + try (ByteArrayInputStream inStream = new ByteArrayInputStream(exportedBytes)) { + AssetInfo info = new AssetInfo(null, null) { + @Override + public InputStream openStream() { + return inStream; + } + }; + importer.load(info); // this is where assertions will fail if loaded data does not match saved data. + } catch (IOException e) { + Assert.fail(exporter.getClass().getSimpleName() + ": " + e.toString()); + } + } + } + + // test data. I tried to include as many edge cases as I could think of. + private static final byte[] testByteArray = new byte[] {Byte.MIN_VALUE, Byte.MAX_VALUE}; + private static final short[] testShortArray = new short[] {Short.MIN_VALUE, Short.MAX_VALUE}; + private static final int[] testIntArray = new int[] {Integer.MIN_VALUE, Integer.MAX_VALUE}; + private static final long[] testLongArray = new long[] {Long.MIN_VALUE, Long.MAX_VALUE}; + private static final float[] testFloatArray = new float[] { + Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL, + Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN + }; + private static final double[] testDoubleArray = new double[] { + Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL, + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN + }; + private static final boolean[] testBooleanArray = new boolean[] {false, true}; + private static final String[] testStringArray = new String[] { + "hello, world!", + null, + "", + " ", // blank string (whitespace) + "mind the gap", // multiple consecutive spaces (some xml processors would normalize this to a single space) + //new String(new char[10_000_000]).replace('\0', 'a'), // long string. kinda slows down the test too much so I'm leaving it out for now. + "\t", + "\n", + "\r", + "hello こんにちは 你好 Здравствуйте 안녕하세요 🙋", + "' " < > &", // xml entities + // xml metacharacters + "\'", + "\"", + "<", + ">", + "&", + "", + "]]>" // xml close cdata + }; + + private static final Savable[] testSavableArray = new Savable[] { + new Vector3f(0f, 1f, 2f), + null, + new Quaternion(0f, 1f, 2f, 3f), + new Transform(new Vector3f(1f, 2f, 3f), new Quaternion(1f, 2f, 3f, 4f), new Vector3f(1f, 2f, 3f)), + new Matrix4f() + }; + + private static final byte[][] testByteArray2D = new byte[][] { + testByteArray, + null, + new byte[0] + }; + + private static final short[][] testShortArray2D = new short[][] { + testShortArray, + null, + new short[0] + }; + + private static final int[][] testIntArray2D = new int[][] { + testIntArray, + null, + new int[0] + }; + + private static final long[][] testLongArray2D = new long[][] { + testLongArray, + null, + new long[0] + }; + + private static final float[][] testFloatArray2D = new float[][] { + testFloatArray, + null, + new float[0] + }; + + private static final double[][] testDoubleArray2D = new double[][] { + testDoubleArray, + null, + new double[0] + }; + + private static final boolean[][] testBooleanArray2D = new boolean[][] { + testBooleanArray, + null, + new boolean[0] + }; + + private static final String[][] testStringArray2D = new String[][] { + testStringArray, + null, + new String[0] + }; + + private static final CullHint[] testEnumArray = new CullHint[] { + CullHint.Never, + null, + CullHint.Always, + CullHint.Inherit + }; + + private static final BitSet[] testBitSetArray = new BitSet[] { + BitSet.valueOf("BitSet".getBytes()), + null, + new BitSet() + }; + + private static final Savable[][] testSavableArray2D = new Savable[][] { + testSavableArray, + null, + new Savable[0] + }; + + private static final ByteBuffer testByteBuffer = (ByteBuffer) BufferUtils.createByteBuffer(testByteArray).rewind(); + private static final ShortBuffer testShortBuffer = (ShortBuffer) BufferUtils.createShortBuffer(testShortArray).rewind(); + private static final IntBuffer testIntBuffer = (IntBuffer) BufferUtils.createIntBuffer(testIntArray).rewind(); + private static final FloatBuffer testFloatBuffer = (FloatBuffer) BufferUtils.createFloatBuffer(testFloatArray).rewind(); + + private static final ArrayList testByteBufferArrayList = new ArrayList<>(Arrays.asList( + BufferUtils.createByteBuffer(testByteArray2D[0]), + null, + BufferUtils.createByteBuffer(testByteArray2D[2]) + )); + + private static final ArrayList testFloatBufferArrayList = new ArrayList<>(Arrays.asList( + BufferUtils.createFloatBuffer(testFloatArray2D[0]), + null, + BufferUtils.createFloatBuffer(testFloatArray2D[2]) + )); + + private static final ArrayList testSavableArrayList = new ArrayList<>(Arrays.asList(testSavableArray)); + + @SuppressWarnings("unchecked") + private static final ArrayList[] testSavableArrayListArray = new ArrayList[] { + testSavableArrayList, + null, + new ArrayList() + }; + + // "array" and "list" don't sound like real words anymore + @SuppressWarnings("unchecked") + private static final ArrayList[][] testSavableArrayListArray2D = new ArrayList[][] { + testSavableArrayListArray, + null, + {}, + }; + + private static final Map testSavableMap = new HashMap() {{ + put(Vector3f.UNIT_X, Vector3f.UNIT_X); + put(Vector3f.UNIT_Y, Quaternion.IDENTITY); + put(Vector3f.UNIT_Z, null); + }}; + + private static final Map testStringSavableMap = new HashMap() {{ + put("v", Vector3f.UNIT_X); + put("q", Quaternion.IDENTITY); + put("n", null); + }}; + + private static final IntMap testIntSavableMap = new IntMap(); + static { //IntMap is final so we gotta use a static block here. + testIntSavableMap.put(0, Vector3f.UNIT_X); + testIntSavableMap.put(1, Quaternion.IDENTITY); + testIntSavableMap.put(2, null); + } + + // the rest of this file is inner classes that implement Savable. + // these classes write the test data, then verify that it's the same data in their read() methods. + private static class TestPrimitives implements Savable { + TestPrimitives() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testByteArray.length; i++) + capsule.write(testByteArray[i], "test_byte_" + i, (byte) 0); + + for (int i = 0; i < testShortArray.length; i++) + capsule.write(testShortArray[i], "test_short_" + i, (short) 0); + + for (int i = 0; i < testIntArray.length; i++) + capsule.write(testIntArray[i], "test_int_" + i, 0); + + for (int i = 0; i < testLongArray.length; i++) + capsule.write(testLongArray[i], "test_long_" + i, 0l); + + for (int i = 0; i < testFloatArray.length; i++) + capsule.write(testFloatArray[i], "test_float_" + i, 0f); + + for (int i = 0; i < testDoubleArray.length; i++) + capsule.write(testDoubleArray[i], "test_double_" + i, 0d); + + for (int i = 0; i < testBooleanArray.length; i++) + capsule.write(testBooleanArray[i], "test_boolean_" + i, false); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testByteArray.length; i++) + Assert.assertEquals("readByte()", testByteArray[i], capsule.readByte("test_byte_" + i, (byte) 0)); + + for (int i = 0; i < testShortArray.length; i++) + Assert.assertEquals("readShort()", testShortArray[i], capsule.readShort("test_short_" + i, (short) 0)); + + for (int i = 0; i < testIntArray.length; i++) + Assert.assertEquals("readInt()", testIntArray[i], capsule.readInt("test_int_" + i, 0)); + + for (int i = 0; i < testLongArray.length; i++) + Assert.assertEquals("readLong()", testLongArray[i], capsule.readLong("test_long_" + i, 0l)); + + for (int i = 0; i < testFloatArray.length; i++) + Assert.assertEquals("readFloat()", testFloatArray[i], capsule.readFloat("test_float_" + i, 0f), 0f); + + for (int i = 0; i < testDoubleArray.length; i++) + Assert.assertEquals("readDouble()", testDoubleArray[i], capsule.readDouble("test_double_" + i, 0d), 0d); + + for (int i = 0; i < testBooleanArray.length; i++) + Assert.assertEquals("readBoolean()", testBooleanArray[i], capsule.readBoolean("test_boolean_" + i, false)); + } + } + + private static class TestStrings implements Savable { + TestStrings() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testStringArray.length; i++) { + capsule.write(testStringArray[i], "test_string_" + i, null); + } + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testStringArray.length; i++) { + Assert.assertEquals("readString()", testStringArray[i], capsule.readString("test_string_" + i, null)); + } + } + } + + private static class TestEnums implements Savable { + TestEnums() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testEnumArray.length; i++) { + capsule.write(testEnumArray[i], "test_enum_" + i, null); + } + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testEnumArray.length; i++) { + Assert.assertEquals("readEnum()", testEnumArray[i], capsule.readEnum("test_enum_" + i, CullHint.class, null)); + } + } + } + + private static class TestBitSets implements Savable { + TestBitSets() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testBitSetArray.length; i++) { + capsule.write(testBitSetArray[i], "test_bit_set_" + i, null); + } + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testBitSetArray.length; i++) { + Assert.assertEquals("readBitSet()", testBitSetArray[i], capsule.readBitSet("test_bit_set_" + i, null)); + } + } + } + + private static class TestSavables implements Savable { + TestSavables() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for(int i = 0; i < testSavableArray.length; i++) + capsule.write(testSavableArray[i], "test_savable_" + i, null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for(int i = 0; i < testSavableArray.length; i++) + Assert.assertEquals("readSavable()", testSavableArray[i], capsule.readSavable("test_savable_" + i, null)); + } + } + + private static class TestSavableReferences implements Savable { + TestSavableReferences() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + Vector3f v1 = new Vector3f(1f, 2f, 3f); + Vector3f notV1 = v1.clone(); + + capsule.write(v1, "v1", null); + capsule.write(v1, "also_v1", null); + capsule.write(notV1, "not_v1", null); + + // testing reference loop. this used to cause infinite recursion. + Node n1 = new Node("node_1"); + Node n2 = new Node("node_2"); + + n1.setUserData("node_2", n2); + n2.setUserData("node_1", n1); + + capsule.write(n1, "node_1", null); + capsule.write(n2, "node_2", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Vector3f v1 = (Vector3f) capsule.readSavable("v1", null); + Vector3f alsoV1 = (Vector3f) capsule.readSavable("also_v1", null); + Vector3f notV1 = (Vector3f) capsule.readSavable("not_v1", null); + + Assert.assertTrue("readSavable() savable duplicated, references not preserved.", v1 == alsoV1); + Assert.assertTrue("readSavable() unique savables merged, unexpected shared references.", v1 != notV1); + + Node n1 = (Node) capsule.readSavable("node_1", null); + Node n2 = (Node) capsule.readSavable("node_2", null); + + Assert.assertTrue("readSavable() reference loop not preserved.", n1.getUserData("node_2") == n2 && n2.getUserData("node_1") == n1); + } + } + + private static class TestArrays implements Savable { + TestArrays() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.write(testByteArray, "testByteArray", null); + capsule.write(testShortArray, "testShortArray", null); + capsule.write(testIntArray, "testIntArray", null); + capsule.write(testLongArray, "testLongArray", null); + capsule.write(testFloatArray, "testFloatArray", null); + capsule.write(testDoubleArray, "testDoubleArray", null); + capsule.write(testBooleanArray, "testBooleanArray", null); + capsule.write(testStringArray, "testStringArray", null); + capsule.write(testSavableArray, "testSavableArray", null); + + capsule.write(new byte[0], "emptyByteArray", null); + capsule.write(new short[0], "emptyShortArray", null); + capsule.write(new int[0], "emptyIntArray", null); + capsule.write(new long[0], "emptyLongArray", null); + capsule.write(new float[0], "emptyFloatArray", null); + capsule.write(new double[0], "emptyDoubleArray", null); + capsule.write(new boolean[0], "emptyBooleanArray", null); + capsule.write(new String[0], "emptyStringArray", null); + capsule.write(new Savable[0], "emptySavableArray", null); + + capsule.write(testByteArray2D, "testByteArray2D", null); + capsule.write(testShortArray2D, "testShortArray2D", null); + capsule.write(testIntArray2D, "testIntArray2D", null); + capsule.write(testLongArray2D, "testLongArray2D", null); + capsule.write(testFloatArray2D, "testFloatArray2D", null); + capsule.write(testDoubleArray2D, "testDoubleArray2D", null); + capsule.write(testBooleanArray2D, "testBooleanArray2D", null); + capsule.write(testStringArray2D, "testStringArray2D", null); + capsule.write(testSavableArray2D, "testSavableArray2D", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertArrayEquals("readByteArray()", testByteArray, capsule.readByteArray("testByteArray", null)); + Assert.assertArrayEquals("readShortArray()", testShortArray, capsule.readShortArray("testShortArray", null)); + Assert.assertArrayEquals("readIntArray()", testIntArray, capsule.readIntArray("testIntArray", null)); + Assert.assertArrayEquals("readLongArray()", testLongArray, capsule.readLongArray("testLongArray", null)); + Assert.assertArrayEquals("readFloatArray()", testFloatArray, capsule.readFloatArray("testFloatArray", null), 0f); + Assert.assertArrayEquals("readDoubleArray()", testDoubleArray, capsule.readDoubleArray("testDoubleArray", null), 0d); + Assert.assertArrayEquals("readBooleanArray()", testBooleanArray, capsule.readBooleanArray("testBooleanArray", null)); + Assert.assertArrayEquals("readStringArray()", testStringArray, capsule.readStringArray("testStringArray", null)); + Assert.assertArrayEquals("readSavableArray()", testSavableArray, capsule.readSavableArray("testSavableArray", null)); + + Assert.assertArrayEquals("readByteArray()", new byte[0], capsule.readByteArray("emptyByteArray", null)); + Assert.assertArrayEquals("readShortArray()", new short[0], capsule.readShortArray("emptyShortArray", null)); + Assert.assertArrayEquals("readIntArray()", new int[0], capsule.readIntArray("emptyIntArray", null)); + Assert.assertArrayEquals("readLongArray()", new long[0], capsule.readLongArray("emptyLongArray", null)); + Assert.assertArrayEquals("readFloatArray()", new float[0], capsule.readFloatArray("emptyFloatArray", null), 0f); + Assert.assertArrayEquals("readDoubleArray()", new double[0], capsule.readDoubleArray("emptyDoubleArray", null), 0d); + Assert.assertArrayEquals("readBooleanArray()", new boolean[0], capsule.readBooleanArray("emptyBooleanArray", null)); + Assert.assertArrayEquals("readStringArray()", new String[0], capsule.readStringArray("emptyStringArray", null)); + Assert.assertArrayEquals("readSavableArray()", new Savable[0], capsule.readSavableArray("emptySavableArray", null)); + + Assert.assertArrayEquals("readByteArray2D()", testByteArray2D, capsule.readByteArray2D("testByteArray2D", null)); + Assert.assertArrayEquals("readShortArray2D()", testShortArray2D, capsule.readShortArray2D("testShortArray2D", null)); + Assert.assertArrayEquals("readIntArray2D()", testIntArray2D, capsule.readIntArray2D("testIntArray2D", null)); + Assert.assertArrayEquals("readLongArray2D()", testLongArray2D, capsule.readLongArray2D("testLongArray2D", null)); + Assert.assertArrayEquals("readFloatArray2D()", testFloatArray2D, capsule.readFloatArray2D("testFloatArray2D", null)); + Assert.assertArrayEquals("readDoubleArray2D()", testDoubleArray2D, capsule.readDoubleArray2D("testDoubleArray2D", null)); + Assert.assertArrayEquals("readBooleanArray2D()", testBooleanArray2D, capsule.readBooleanArray2D("testBooleanArray2D", null)); + Assert.assertArrayEquals("readStringArray2D()", testStringArray2D, capsule.readStringArray2D("testStringArray2D", null)); + Assert.assertArrayEquals("readSavableArray2D()", testSavableArray2D, capsule.readSavableArray2D("testSavableArray2D", null)); + } + } + + private static class TestBuffers implements Savable { + TestBuffers() { + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.write(testByteBuffer, "testByteBuffer", null); + capsule.write(testShortBuffer, "testShortBuffer", null); + capsule.write(testIntBuffer, "testIntBuffer", null); + capsule.write(testFloatBuffer, "testFloatBuffer", null); + + capsule.write(BufferUtils.createByteBuffer(0), "emptyByteBuffer", null); + capsule.write(BufferUtils.createShortBuffer(0), "emptyShortBuffer", null); + capsule.write(BufferUtils.createIntBuffer(0), "emptyIntBuffer", null); + capsule.write(BufferUtils.createFloatBuffer(0), "emptyFloatBuffer", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertEquals("readByteBuffer()", testByteBuffer, capsule.readByteBuffer("testByteBuffer", null)); + Assert.assertEquals("readShortBuffer()", testShortBuffer, capsule.readShortBuffer("testShortBuffer", null)); + Assert.assertEquals("readIntBuffer()", testIntBuffer, capsule.readIntBuffer("testIntBuffer", null)); + Assert.assertEquals("readFloatBuffer()", testFloatBuffer, capsule.readFloatBuffer("testFloatBuffer", null)); + + Assert.assertEquals("readByteBuffer()", BufferUtils.createByteBuffer(0), capsule.readByteBuffer("emptyByteBuffer", null)); + Assert.assertEquals("readShortBuffer()", BufferUtils.createShortBuffer(0), capsule.readShortBuffer("emptyShortBuffer", null)); + Assert.assertEquals("readIntBuffer()", BufferUtils.createIntBuffer(0), capsule.readIntBuffer("emptyIntBuffer", null)); + Assert.assertEquals("readFloatBuffer()", BufferUtils.createFloatBuffer(0), capsule.readFloatBuffer("emptyFloatBuffer", null)); + } + } + + private static class TestLists implements Savable { + TestLists() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.writeByteBufferArrayList(testByteBufferArrayList, "testByteBufferArrayList", null); + capsule.writeFloatBufferArrayList(testFloatBufferArrayList, "testFloatBufferArrayList", null); + capsule.writeSavableArrayList(testSavableArrayList, "testSavableArrayList", null); + capsule.writeSavableArrayListArray(testSavableArrayListArray, "testSavableArrayListArray", null); + capsule.writeSavableArrayListArray2D(testSavableArrayListArray2D, "testSavableArrayListArray2D", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertEquals("readByteBufferArrayList()", testByteBufferArrayList, capsule.readByteBufferArrayList("testByteBufferArrayList", null)); + Assert.assertEquals("readFloatBufferArrayList()", testFloatBufferArrayList, capsule.readFloatBufferArrayList("testFloatBufferArrayList", null)); + Assert.assertEquals("readSavableArrayList()", testSavableArrayList, capsule.readSavableArrayList("testSavableArrayList", null)); + Assert.assertEquals("readSavableArrayListArray()", testSavableArrayListArray, capsule.readSavableArrayListArray("testSavableArrayListArray", null)); + Assert.assertEquals("readSavableArrayListArray2D()", testSavableArrayListArray2D, capsule.readSavableArrayListArray2D("testSavableArrayListArray2D", null)); + } + } + + private static class TestMaps implements Savable { + TestMaps() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.writeSavableMap(testSavableMap, "testSavableMap", null); + capsule.writeStringSavableMap(testStringSavableMap, "testStringSavableMap", null); + capsule.writeIntSavableMap(testIntSavableMap, "testIntSavableMap", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertEquals("readSavableMap()", testSavableMap, capsule.readSavableMap("testSavableMap", null)); + Assert.assertEquals("readStringSavableMap()", testStringSavableMap, capsule.readStringSavableMap("testStringSavableMap", null)); + + // IntMap does not implement equals() so we have to do it manually + IntMap loadedIntMap = capsule.readIntSavableMap("testIntSavableMap", null); + Iterator iterator = testIntSavableMap.iterator(); + while(iterator.hasNext()) { + IntMap.Entry entry = (IntMap.Entry) iterator.next(); + Assert.assertEquals("readIntSavableMap()", entry.getValue(), loadedIntMap.get(entry.getKey())); + } + } + } +} diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java index 5e2466a6bb..1d91ca903b 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java @@ -55,11 +55,10 @@ * @author blaine */ public class DOMInputCapsule implements InputCapsule { - private static final Logger logger = - Logger.getLogger(DOMInputCapsule.class .getName()); + private static final Logger logger = Logger.getLogger(DOMInputCapsule.class .getName()); private Document doc; - private Element currentElem; + private Element currentElement; private XMLImporter importer; private boolean isAtRoot = true; private Map referencedSavables = new HashMap<>(); @@ -70,1370 +69,862 @@ public class DOMInputCapsule implements InputCapsule { public DOMInputCapsule(Document doc, XMLImporter importer) { this.doc = doc; this.importer = importer; - currentElem = doc.getDocumentElement(); + currentElement = doc.getDocumentElement(); // file version is always unprefixed for backwards compatibility - String version = currentElem.getAttribute("format_version"); + String version = currentElement.getAttribute("format_version"); importer.formatVersion = version.equals("") ? 0 : Integer.parseInt(version); } @Override public int getSavableVersion(Class desiredClass) { if (classHierarchyVersions != null){ - return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, - classHierarchyVersions, importer.getFormatVersion()); + return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, classHierarchyVersions, importer.getFormatVersion()); }else{ return 0; } } - - private static String decodeString(String s) { - if (s == null) { + + private Element findChildElement(String name) { + if (currentElement == null) { return null; } - s = s.replaceAll("\\"", "\"").replaceAll("\\<", "<").replaceAll("\\&", "&"); - return s; - } - - private Element findFirstChildElement(Element parent) { - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element))) { + Node ret = currentElement.getFirstChild(); + while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { ret = ret.getNextSibling(); } return (Element) ret; } - private Element findChildElement(Element parent, String name) { - if (parent == null) { + // helper method to reduce duplicate code. checks that number of tokens in the "data" attribute matches the "size" attribute + // and returns an array of parsed primitives. + private Object readPrimitiveArrayHelper(Element element, String primType) throws IOException { + if (element == null) { return null; } - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { - ret = ret.getNextSibling(); + + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; } - return (Element) ret; - } - private Element findNextSiblingElement(Element current) { - Node ret = current.getNextSibling(); - while (ret != null) { - if (ret instanceof Element) { - return (Element) ret; + String[] tokens = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_DATA)); + + if(!sizeString.isEmpty()) { + try { + int requiredSize = Integer.parseInt(sizeString); + if (tokens.length != requiredSize) { + throw new IOException("Wrong token count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + tokens.length); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + } + + try { + switch (primType) { + case "byte": + byte[] byteArray = new byte[tokens.length]; + for (int i = 0; i < tokens.length; i++) byteArray[i] = Byte.parseByte(tokens[i]); + return byteArray; + case "short": + short[] shortArray = new short[tokens.length]; + for (int i = 0; i < tokens.length; i++) shortArray[i] = Short.parseShort(tokens[i]); + return shortArray; + case "int": + int[] intArray = new int[tokens.length]; + for (int i = 0; i < tokens.length; i++) intArray[i] = Integer.parseInt(tokens[i]); + return intArray; + case "long": + long[] longArray = new long[tokens.length]; + for (int i = 0; i < tokens.length; i++) longArray[i] = Long.parseLong(tokens[i]); + return longArray; + case "float": + float[] floatArray = new float[tokens.length]; + for (int i = 0; i < tokens.length; i++) floatArray[i] = Float.parseFloat(tokens[i]); + return floatArray; + case "double": + double[] doubleArray = new double[tokens.length]; + for (int i = 0; i < tokens.length; i++) doubleArray[i] = Double.parseDouble(tokens[i]); + return doubleArray; + case "boolean": + boolean[] booleanArray = new boolean[tokens.length]; + for (int i = 0; i < tokens.length; i++) booleanArray[i] = Boolean.parseBoolean(tokens[i]); + return booleanArray; + default: + throw new IOException(); // will never happen + } + } catch(NumberFormatException nfe) { + throw new IOException(nfe); + } + } + + // helper method to reduce duplicate code. checks that number of child elements matches the "size" attribute + // and returns a convenient list of child elements. + private List getObjectArrayElements(Element element) throws IOException { + if (element == null) { + return null; + } + + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; + } + + NodeList nodes = element.getChildNodes(); + + List elements = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node instanceof Element) { + elements.add((Element) node); } - ret = ret.getNextSibling(); } - return null; + + try { + int requiredSize = Integer.parseInt(sizeString); + if (elements.size() != requiredSize) { + throw new IOException("DOMInputCapsule.getObjectArrayElements(): Wrong element count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + elements.size()); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + + return elements; } @Override public byte readByte(String name, byte defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Byte.parseByte(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Byte.parseByte(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override public byte[] readByteArray(String name, byte[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of bytes for '" + name - + "'. size says " + requiredSize - + ", data contains " - + strings.length); - } - byte[] tmp = new byte[strings.length]; - for (int i = 0; i < strings.length; i++) { - tmp[i] = Byte.parseByte(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + byte[] array = (byte[]) readPrimitiveArrayHelper(findChildElement(name), "byte"); + return array != null ? array : defVal; } @Override public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List byteArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - byteArrays.add(readByteArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (byteArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + byteArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return byteArrays.toArray(new byte[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + byte[][] arrays = new byte[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (byte[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "byte"); } + + return arrays; } @Override - public int readInt(String name, int defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Integer.parseInt(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public short readShort(String name, short defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } - } - @Override - public int[] readIntArray(String name, int[] defVal) throws IOException { try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of ints for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - int[] tmp = new int[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Integer.parseInt(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Short.parseShort(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - - + public short[] readShortArray(String name, short[] defVal) throws IOException { + short[] array = (short[]) readPrimitiveArrayHelper(findChildElement(name), "short"); + return array != null ? array : defVal; + } + @Override + public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - NodeList nodes = currentElem.getChildNodes(); - List intArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - intArrays.add(readIntArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (intArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + intArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return intArrays.toArray(new int[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + short[][] arrays = new short[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (short[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "short"); } + + return arrays; } @Override - public float readFloat(String name, float defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public int readInt(String name, int defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Float.parseFloat(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Integer.parseInt(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public float[] readFloatArray(String name, float[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of floats for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - float[] tmp = new float[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Float.parseFloat(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + public int[] readIntArray(String name, int[] defVal) throws IOException { + int[] array = (int[]) readPrimitiveArrayHelper(findChildElement(name), "int"); + return array != null ? array : defVal; } @Override - public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { - /* Why does this one method ignore the 'size attr.? */ - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - int size_outer = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); - int size_inner = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); + public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - float[][] tmp = new float[size_outer][size_inner]; + if (arrayEntryElements == null) { + return defVal; + } - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - for (int i = 0; i < size_outer; i++) { - tmp[i] = new float[size_inner]; - for (int k = 0; k < size_inner; k++) { - tmp[i][k] = Float.parseFloat(strings[i]); - } - } - return tmp; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + int[][] arrays = new int[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (int[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "int"); } + + return arrays; } @Override - public double readDouble(String name, double defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public long readLong(String name, long defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Double.parseDouble(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Long.parseLong(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public double[] readDoubleArray(String name, double[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of doubles for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - double[] tmp = new double[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Double.parseDouble(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public long[] readLongArray(String name, long[] defVal) throws IOException { + long[] array = (long[]) readPrimitiveArrayHelper(findChildElement(name), "long"); + return array != null ? array : defVal; } @Override - public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List doubleArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - doubleArrays.add(readDoubleArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (doubleArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + doubleArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return doubleArrays.toArray(new double[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + long[][] arrays = new long[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (long[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "long"); } + + return arrays; } @Override - public long readLong(String name, long defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public float readFloat(String name, float defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Long.parseLong(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Float.parseFloat(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public long[] readLongArray(String name, long[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of longs for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - long[] tmp = new long[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Long.parseLong(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public float[] readFloatArray(String name, float[] defVal) throws IOException { + float[] array = (float[]) readPrimitiveArrayHelper(findChildElement(name), "float"); + return array != null ? array : defVal; } @Override - public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List longArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - longArrays.add(readLongArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (longArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + longArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return longArrays.toArray(new long[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + float[][] arrays = new float[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (float[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "float"); } + + return arrays; } @Override - public short readShort(String name, short defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public double readDouble(String name, double defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Short.parseShort(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Double.parseDouble(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public short[] readShortArray(String name, short[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of shorts for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - short[] tmp = new short[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Short.parseShort(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public double[] readDoubleArray(String name, double[] defVal) throws IOException { + double[] array = (double[]) readPrimitiveArrayHelper(findChildElement(name), "double"); + return array != null ? array : defVal; } @Override - public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List shortArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - shortArrays.add(readShortArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (shortArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + shortArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return shortArrays.toArray(new short[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + double[][] arrays = new double[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (double[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "double"); } + + return arrays; } @Override public boolean readBoolean(String name, boolean defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Boolean.parseBoolean(tmpString); - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } + + //parseBoolean doesn't throw anything, just returns false if the string isn't "true", ignoring case. + return Boolean.parseBoolean(attribute); } @Override public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of bools for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - boolean[] tmp = new boolean[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Boolean.parseBoolean(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + boolean[] array = (boolean[]) readPrimitiveArrayHelper(findChildElement(name), "boolean"); + return array != null ? array : defVal; } @Override public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List booleanArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - booleanArrays.add(readBooleanArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (booleanArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + booleanArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return booleanArrays.toArray(new boolean[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + boolean[][] arrays = new boolean[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (boolean[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "boolean"); } + + return arrays; } @Override public String readString(String name, String defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return decodeString(tmpString); - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String attribute = null; + + // Element.getAttribute() returns an empty string if the specified attribute does not exist. + // see https://www.w3.org/2003/01/dom2-javadoc/org/w3c/dom/Element.html#getAttribute_java.lang.String_ + // somewhat confusing since the w3c JS api equivalent returns null as one would expect. + // https://www.w3schools.com/jsref/met_element_getattribute.asp + if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElement, name)) { + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); } + + if (attribute == null) { + return defVal; + } + + return attribute; } @Override public String[] readStringArray(String name, String[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = tmpEl.getChildNodes(); - List strings = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("String")) { - // Very unsafe assumption - strings.add(((Element) n).getAttributeNode("value").getValue()); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + strings.size()); - } - return strings.toArray(new String[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + List arrayElements = getObjectArrayElements(findChildElement(name)); + + if (arrayElements == null) { + return defVal; } + + Element oldElement = currentElement; + + String[] array = new String[arrayElements.size()]; + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + array[i] = readString("value", null); + } + + currentElement = oldElement; + + return array; } @Override public String[][] readStringArray2D(String name, String[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List stringArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - stringArrays.add(readStringArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (stringArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + stringArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return stringArrays.toArray(new String[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + Element outerArrayElement = findChildElement(name); + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + if (innerArrayElements == null) { + return defVal; } + + currentElement = outerArrayElement; + + String[][] arrays = new String[innerArrayElements.size()][]; + for (int i = 0; i < innerArrayElements.size(); i++) { + arrays[i] = readStringArray(innerArrayElements.get(i).getTagName(), null); + } + + currentElement = (Element) currentElement.getParentNode(); + + return arrays; } @Override - public BitSet readBitSet(String name, BitSet defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public > T readEnum(String name, Class enumType, T defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - BitSet set = new BitSet(); - String[] strings = parseTokens(tmpString); - for (int i = 0; i < strings.length; i++) { - int isSet = Integer.parseInt(strings[i]); - if (isSet == 1) { - set.set(i); - } - } - return set; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Enum.valueOf(enumType, attribute); + } catch (IllegalArgumentException | NullPointerException e) { + throw new IOException(e); } } @Override - public Savable readSavable(String name, Savable defVal) throws IOException { - Savable ret = defVal; - if (name != null && name.equals("")) - logger.warning("Reading Savable String with name \"\"?"); - try { - Element tmpEl = null; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - } else if (isAtRoot) { - tmpEl = doc.getDocumentElement(); - isAtRoot = false; - } else { - tmpEl = findFirstChildElement(currentElem); - } - currentElem = tmpEl; - ret = readSavableFromCurrentElem(defVal); - if (currentElem.getParentNode() instanceof Element) { - currentElem = (Element) currentElem.getParentNode(); - } else { - currentElem = null; + public BitSet readBitSet(String name, BitSet defVal) throws IOException { + String attribute = null; + + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute == null || attribute.isEmpty()) { + return defVal; + } + + String[] strings = parseTokens(attribute); + BitSet bitSet = new BitSet(); + for (int i = 0; i < strings.length; i++) { + if (strings[i].equals("1")) { + bitSet.set(i); } - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } - return ret; - } - private Savable readSavableFromCurrentElem(Savable defVal) throws - InstantiationException, ClassNotFoundException, - NoSuchMethodException, InvocationTargetException, - IOException, IllegalAccessException { - Savable ret = defVal; - Savable tmp = null; + return bitSet; + } - if (currentElem == null || currentElem.getNodeName().equals("null")) { - return null; + private Savable readSavableFromCurrentElement(Savable defVal) throws IOException { + if (currentElement == null || !currentElement.hasAttributes()) { + return defVal; } - String reference = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "ref"); - if (reference.length() > 0) { - ret = referencedSavables.get(reference); + + String reference = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_REFERENCE); + if (!reference.isEmpty()) { + return referencedSavables.get(reference); } else { - String className = currentElem.getNodeName(); - if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElem, "class")) { - className = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "class"); + // for backwards compatibility with old XML files. previous version of DOMOutputCapsule would only write the class attribute + // if the element name wasn't the class name. + String className = currentElement.getNodeName(); + if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_CLASS)) { + className = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_CLASS); } else if (defVal != null) { className = defVal.getClass().getName(); } - tmp = SavableClassUtil.fromName(className); + + Savable tmp = null; + try { + tmp = SavableClassUtil.fromName(className); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new IOException(e); + } - String versionsStr = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "savable_versions"); + String versionsStr = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_SAVABLE_VERSIONS); if (versionsStr != null && !versionsStr.equals("")){ String[] versionStr = versionsStr.split(","); classHierarchyVersions = new int[versionStr.length]; - for (int i = 0; i < classHierarchyVersions.length; i++){ - classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + try { + for (int i = 0; i < classHierarchyVersions.length; i++) { + classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + } + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } }else{ classHierarchyVersions = null; } - String refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "reference_ID"); - if (refID.length() < 1) refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "id"); - if (refID.length() > 0) referencedSavables.put(refID, tmp); + String refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_REFERENCE_ID); + + // what does this line do? guessing backwards compatibility? + if (refID.isEmpty()) refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "id"); + + if (!refID.isEmpty()) referencedSavables.put(refID, tmp); + if (tmp != null) { // Allows reading versions from this savable savable = tmp; + tmp.read(importer); - ret = tmp; + + return tmp; + } else { + return defVal; } } + } + + @Override + public Savable readSavable(String name, Savable defVal) throws IOException { + Savable ret = defVal; + + if (name != null && name.equals("")) + logger.warning("Reading Savable String with name \"\"?"); + + Element old = currentElement; + + if (isAtRoot) { + currentElement = doc.getDocumentElement(); + isAtRoot = false; + } else { + currentElement = findChildElement(name); + } + + ret = readSavableFromCurrentElement(defVal); + + currentElement = old; + return ret; } @Override public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException { - Savable[] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + Element arrayElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - List savables = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException("Wrong number of Savables for '" - + name + "'. size says " + requiredSize - + ", data contains " + savables.size()); - } - ret = savables.toArray(new Savable[0]); - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + if (arrayElement == null || !arrayElement.hasAttributes()) { + return defVal; + } + + List arrayElements = getObjectArrayElements(arrayElement); + + Savable[] savableArray = new Savable[arrayElements.size()]; + + Element old = currentElement; + + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + savableArray[i] = readSavableFromCurrentElement(null); } + + currentElement = old; + + return savableArray; } @Override public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException { - Savable[][] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + Element outerArrayElement = findChildElement(name); - int size_outer = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); - int size_inner = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); + if (outerArrayElement == null || !outerArrayElement.hasAttributes()) { + return defVal; + } - Savable[][] tmp = new Savable[size_outer][size_inner]; - currentElem = findFirstChildElement(tmpEl); - for (int i = 0; i < size_outer; i++) { - for (int j = 0; j < size_inner; j++) { - tmp[i][j] = (readSavableFromCurrentElem(null)); - if (i == size_outer - 1 && j == size_inner - 1) { - break; - } - currentElem = findNextSiblingElement(currentElem); - } + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + Savable[][] savableArray2D = new Savable[innerArrayElements.size()][]; + + Element old = currentElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + List savableElements = getObjectArrayElements(innerArrayElements.get(i)); + + if (savableElements == null) { + continue; + } + + savableArray2D[i] = new Savable[savableElements.size()]; + for (int j = 0; j < savableElements.size(); j++) { + currentElement = savableElements.get(j); + savableArray2D[i][j] = readSavableFromCurrentElement(null); } - ret = tmp; - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = old; + + return savableArray2D; } @Override - @SuppressWarnings("unchecked") - public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - ArrayList savables = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException( - "Wrong number of Savable arrays for '" + name - + "'. size says " + requiredSize - + ", data contains " + savables.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return savables; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + byte[] array = (byte[]) readPrimitiveArrayHelper(element, "byte"); + + if (array == null) { + return defVal; } + + return (ByteBuffer) BufferUtils.createByteBuffer(array.length).put(array).rewind(); } @Override - @SuppressWarnings("unchecked") - public ArrayList[] readSavableArrayListArray( - String name, ArrayList[] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - int requiredSize = (sizeString.length() > 0) - ? Integer.parseInt(sizeString) - : -1; - - ArrayList sal; - List> savableArrayLists = - new ArrayList<>(); - int i = -1; - while (true) { - sal = readSavableArrayList("SavableArrayList_" + ++i, null); - if (sal == null && savableArrayLists.size() >= requiredSize) - break; - savableArrayLists.add(sal); - } + public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { + Element element = findChildElement(name); + + short[] array = (short[]) readPrimitiveArrayHelper(element, "short"); - if (requiredSize > -1 && savableArrayLists.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + savableArrayLists.size()); - currentElem = (Element) tmpEl.getParentNode(); - return savableArrayLists.toArray(new ArrayList[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + if (array == null) { + return defVal; } + + return (ShortBuffer) BufferUtils.createShortBuffer(array.length).put(array).rewind(); } @Override - @SuppressWarnings("unchecked") - public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - - ArrayList[] arr; - List[]> sall = new ArrayList<>(); - int i = -1; - while ((arr = readSavableArrayListArray( - "SavableArrayListArray_" + ++i, null)) != null) sall.add(arr); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (sall.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + sall.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return sall.toArray(new ArrayList[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { + Element element = findChildElement(name); + + int[] array = (int[]) readPrimitiveArrayHelper(element, "int"); + + if (array == null) { + return defVal; } + + return (IntBuffer) BufferUtils.createIntBuffer(array.length).put(array).rewind(); } @Override - public ArrayList readFloatBufferArrayList( - String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - ArrayList tmp = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - tmp.add(readFloatBuffer(null, null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (tmp.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + tmp.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + float[] array = (float[]) readPrimitiveArrayHelper(element, "float"); + + if (array == null) { + return defVal; } + + return (FloatBuffer) BufferUtils.createFloatBuffer(array.length).put(array).rewind(); } @Override - public Map readSavableMap(String name, Map defVal) throws IOException { - Map ret; - Element tempEl; + public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { + byte[][] byteArray2D = readByteArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; + if (byteArray2D == null) { + return defVal; } - ret = new HashMap(); - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); - Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); - ret.put(key, val); - } + ArrayList byteBufferList = new ArrayList<>(byteArray2D.length); + for (byte[] byteArray : byteArray2D) { + if (byteArray == null) { + byteBufferList.add(null); + } else { + byteBufferList.add((ByteBuffer) BufferUtils.createByteBuffer(byteArray.length).put(byteArray).rewind()); + } } - currentElem = (Element) tempEl.getParentNode(); - return ret; + + return byteBufferList; } @Override - public Map readStringSavableMap(String name, Map defVal) throws IOException { - Map ret = null; - Element tempEl; + public ArrayList readFloatBufferArrayList(String name, ArrayList defVal) throws IOException { + float[][] floatArray2D = readFloatArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new HashMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - String key = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "key"); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; + if (floatArray2D == null) { + return defVal; + } + + ArrayList floatBufferList = new ArrayList<>(floatArray2D.length); + for (float[] floatArray : floatArray2D) { + if (floatArray == null) { + floatBufferList.add(null); + } else { + floatBufferList.add((FloatBuffer) BufferUtils.createFloatBuffer(floatArray.length).put(floatArray).rewind()); } - currentElem = (Element) tempEl.getParentNode(); - return ret; + } + + return floatBufferList; } @Override - public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { - IntMap ret = null; - Element tempEl; + @SuppressWarnings("unchecked") + public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { + Savable[] savableArray = readSavableArray(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new IntMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - int key = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "key")); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; - } - currentElem = (Element) tempEl.getParentNode(); - return ret; + if (savableArray == null) { + return defVal; + } + + return new ArrayList(Arrays.asList(savableArray)); } - /** - * reads from currentElem if name is null - */ @Override - public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of float buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + @SuppressWarnings("unchecked") + public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException { + Savable[][] savableArray2D = readSavableArray2D(name, null); + + if (savableArray2D == null) { + return defVal; + } + + ArrayList[] savableArrayListArray = new ArrayList[savableArray2D.length]; + for (int i = 0; i < savableArray2D.length; i++) { + if (savableArray2D[i] != null) { + savableArrayListArray[i] = new ArrayList(Arrays.asList(savableArray2D[i])); } - FloatBuffer tmp = BufferUtils.createFloatBuffer(strings.length); - for (String s : strings) tmp.put(Float.parseFloat(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + return savableArrayListArray; } @Override - public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @SuppressWarnings("unchecked") + public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { + Element outerArrayElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of int buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (outerArrayElement == null) { + return defVal; + } + + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + ArrayList[][] savableArrayListArray2D = new ArrayList[innerArrayElements.size()][]; + + Element old = currentElement; + currentElement = outerArrayElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + if (innerArrayElements.get(i) != null) { + + savableArrayListArray2D[i] = readSavableArrayListArray(innerArrayElements.get(i).getTagName(), null); } - IntBuffer tmp = BufferUtils.createIntBuffer(strings.length); - for (String s : strings) tmp.put(Integer.parseInt(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = old; + + return savableArrayListArray2D; } @Override - public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public Map readSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of byte buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); + Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); + ret.put(key, val); } - ByteBuffer tmp = BufferUtils.createByteBuffer(strings.length); - for (String s : strings) tmp.put(Byte.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; } @Override - public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public Map readStringSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of short buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + String key = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key"); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - ShortBuffer tmp = BufferUtils.createShortBuffer(strings.length); - for (String s : strings) tmp.put(Short.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; } @Override - public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - ArrayList tmp = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - tmp.add(readByteBuffer(null, null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (tmp.size() != requiredSize) - throw new IOException("Wrong number of short buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + tmp.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + if (mapElement == null) { + return defVal; } - @Override - public > T readEnum(String name, Class enumType, - T defVal) throws IOException { - T ret = defVal; - try { - String eVal = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (eVal != null && eVal.length() > 0) { - ret = Enum.valueOf(enumType, eVal); + IntMap ret = new IntMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + int key = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key")); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + return ret; - } + } private static final String[] zeroStrings = new String[0]; @@ -1443,5 +934,4 @@ protected String[] parseTokens(String inString) { ? zeroStrings : outStrings; } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java index fe02a2d0e7..58d1713de9 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java @@ -39,13 +39,13 @@ import com.jme3.export.SavableClassUtil; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; +import java.lang.reflect.Array; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.*; -import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -56,8 +56,6 @@ * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 */ public class DOMOutputCapsule implements OutputCapsule { - - private static final String dataAttributeName = "data"; private Document doc; private Element currentElement; private JmeExporter exporter; @@ -90,397 +88,258 @@ private Element appendElement(String name) { return ret; } - private static String encodeString(String s) { - if (s == null) { - return null; - } - s = s.replaceAll("\\&", "&") - .replaceAll("\\\"", """) - .replaceAll("\\<", "<"); - return s; - } + // helper function to reduce duplicate code. uses reflection to write an array of any primitive type. + // also has optional position argument for writing buffers. + private void writePrimitiveArrayHelper(Object value, String name) throws IOException { + StringBuilder sb = new StringBuilder(); - @Override - public void write(byte value, String name, byte defVal) throws IOException { - if (value == defVal) { - return; + for(int i = 0; i < Array.getLength(value); i++) { + sb.append(Array.get(value, i)); + sb.append(" "); } - XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); - } - @Override - public void write(byte[] value, String name, byte[] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { - value = defVal; - } - for (byte b : value) { - buf.append(b); - buf.append(" "); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); - } + // remove last space + sb.setLength(Math.max(0, sb.length() - 1)); + + appendElement(name); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(Array.getLength(value))); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_DATA, sb.toString()); - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); currentElement = (Element) currentElement.getParentNode(); } - @Override - public void write(byte[][] value, String name, byte[][] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { - value = defVal; - } - for (byte[] bs : value) { - for (byte b : bs) { - buf.append(b); - buf.append(" "); + // helper function to reduce duplicate code. uses the above helper to write a 2d array of any primitive type. + private void writePrimitiveArray2DHelper(Object[] value, String name) throws IOException { + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + + // tag names are not used for anything by DOMInputCapsule, but for the sake of readability, it's nice to know what type of array this is. + String childNamePrefix = value.getClass().getComponentType().getSimpleName().toLowerCase(); + childNamePrefix = childNamePrefix.replace("[]", "_array_"); + + for (int i = 0; i < value.length; i++) { + String childName = childNamePrefix + i; + + if (value[i] != null) { + writePrimitiveArrayHelper(value[i], childName); + } else { + // empty tag + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); } - buf.append(" "); - } - - if (buf.length() > 1) { - //remove last spaces - buf.setLength(buf.length() - 2); - } else if (buf.length() > 0) { - buf.setLength(buf.length() - 1); } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size_outer", String.valueOf(value.length)); - XMLUtils.setAttribute(el, "size_inner", String.valueOf(value[0].length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(int value, String name, int defVal) throws IOException { - if (value == defVal) { - return; + public void write(byte value, String name, byte defVal) throws IOException { + if (value != defVal) { + XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); } - XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); } @Override - public void write(int[] value, String name, int[] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { return; } - if (Arrays.equals(value, defVal)) { return; } - - for (int b : value) { - buf.append(b); - buf.append(" "); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(byte[] value, String name, byte[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(int[][] value, String name, int[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); - } + public void write(short[] value, String name, short[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", value == null ? "0" : String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(float[][] value, String name, float[][] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - for (float[] bs : value) { - for(float b : bs){ - buf.append(b); - buf.append(" "); - } - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(short[][] value, String name, short[][] defVal) throws IOException { + if (!Arrays.deepEquals(value, defVal)) { + writePrimitiveArray2DHelper(value, name); } - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size_outer", String.valueOf(value.length)); - XMLUtils.setAttribute(el, "size_inner", String.valueOf(value[0].length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(double value, String name, double defVal) throws IOException { - if (value == defVal) { - return; + public void write(int value, String name, int defVal) throws IOException { + if (value != defVal) { + XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); } - XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); } @Override - public void write(double[] value, String name, double[] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { - value = defVal; - } - for (double b : value) { - buf.append(b); - buf.append(" "); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(int[] value, String name, int[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(double[][] value, String name, double[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override public void write(long[][] value, String name, long[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(float[] value, String name, float[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } + } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); + @Override + public void write(float[][] value, String name, float[][] defVal) throws IOException { + if (!Arrays.deepEquals(value, defVal)) { + writePrimitiveArray2DHelper(value, name); + } } @Override - public void write(short[][] value, String name, short[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; + public void write(double value, String name, double defVal) throws IOException { + if (value != defVal) { + XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); + } + } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); + @Override + public void write(double[] value, String name, double[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); + } + } - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.length)); - - for (int i=0; i= 0; i = value.nextSetBit(i + 1)) { - buf.append(i); - buf.append(" "); + for (int i = 0; i < value.size(); i++) { + buf.append(value.get(i) ? "1 " : "0 "); } if (buf.length() > 0) { @@ -500,49 +359,41 @@ public void write(BitSet value, String name, BitSet defVal) throws IOException { } XMLUtils.setAttribute(currentElement, name, buf.toString()); - } @Override - public void write(Savable object, String name, Savable defVal) throws IOException { - if (object == null) { - return; - } - if (object.equals(defVal)) { + public void write(Savable value, String name, Savable defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } Element old = currentElement; - Element el = writtenSavables.get(object); - - String className = null; - if(!object.getClass().getName().equals(name)){ - className = object.getClass().getName(); - } - try { - doc.createElement(name); - } catch (DOMException e) { - // Ridiculous fallback behavior. - // Would be far better to throw than to totally disregard the - // specified "name" and write a class name instead! - // (Besides the fact we are clobbering the managed .getClassTag()). - name = "Object"; - className = object.getClass().getName(); - } - - if (el != null) { - String refID = el.getAttribute("reference_ID"); - if (refID.length() == 0) { - refID = object.getClass().getName() + "@" + object.hashCode(); - XMLUtils.setAttribute(el, "reference_ID", refID); + + // no longer tries to use class name as element name. that makes things unnecessarily complicated. + + Element refElement = writtenSavables.get(value); + // this object has already been written, so make an element that refers to the existing one. + if (refElement != null) { + String refID = XMLUtils.getAttribute(FormatVersion.VERSION, refElement, XMLExporter.ATTRIBUTE_REFERENCE_ID); + + // add the reference_ID to the referenced element if it didn't already have it + if (refID.isEmpty()) { + refID = value.getClass().getName() + "@" + value.hashCode(); + XMLUtils.setAttribute(refElement, XMLExporter.ATTRIBUTE_REFERENCE_ID, refID); } - el = appendElement(name); - XMLUtils.setAttribute(el, "ref", refID); + + appendElement(name); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_REFERENCE, refID); } else { - el = appendElement(name); + appendElement(name); + + // this now always writes the class attribute even if the class name is also the element name. + // for backwards compatibility, DOMInputCapsule will still try to get the class name from the element name if the + // attribute isn't found. + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_CLASS, value.getClass().getName()); // jME3 NEW: Append version number(s) - int[] versions = SavableClassUtil.getSavableVersions(object.getClass()); + int[] versions = SavableClassUtil.getSavableVersions(value.getClass()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < versions.length; i++){ sb.append(versions[i]); @@ -550,376 +401,299 @@ public void write(Savable object, String name, Savable defVal) throws IOExceptio sb.append(", "); } } - XMLUtils.setAttribute(el, "savable_versions", sb.toString()); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SAVABLE_VERSIONS, sb.toString()); - writtenSavables.put(object, el); - object.write(exporter); - } - if(className != null){ - XMLUtils.setAttribute(el, "class", className); + writtenSavables.put(value, currentElement); + + value.write(exporter); } currentElement = old; } @Override - public void write(Savable[] objects, String name, Savable[] defVal) throws IOException { - if (objects == null) { - return; - } - if (Arrays.equals(objects, defVal)) { + public void write(Savable[] value, String name, Savable[] defVal) throws IOException { + if (value == null || Arrays.equals(value, defVal)) { return; } Element old = currentElement; - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(objects.length)); - for (int i = 0; i < objects.length; i++) { - Savable o = objects[i]; + + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + Savable o = value[i]; + String elementName = "savable_" + i; if(o == null){ - //renderStateList has special loading code, so we can leave out the null values + // renderStateList has special loading code, so we can leave out the null values if(!name.equals("renderStateList")){ - Element before = currentElement; - appendElement("null"); - currentElement = before; + Element before = currentElement; + appendElement(elementName); + currentElement = before; } }else{ - write(o, o.getClass().getName(), null); + write(o, elementName, null); } } + currentElement = old; } @Override public void write(Savable[][] value, String name, Savable[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size_outer", String.valueOf(value.length)); - XMLUtils.setAttribute(el, "size_inner", String.valueOf(value[0].length)); - for (Savable[] bs : value) { - for(Savable b : bs){ - write(b, b.getClass().getSimpleName(), null); - } - } - currentElement = (Element) currentElement.getParentNode(); - } - - @Override - public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + if (value == null || Arrays.deepEquals(value, defVal)) { return; } + Element old = currentElement; - Element el = appendElement(name); - currentElement = el; - XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); - for (Object o : array) { - if(o == null) { - continue; - } - else if (o instanceof Savable) { - Savable s = (Savable) o; - write(s, s.getClass().getName(), null); + + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + String childName = "savable_array_" + i; + if (value[i] != null) { + write(value[i], childName, null); } else { - throw new ClassCastException("Not a Savable instance: " + o); + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); } } + currentElement = old; } @Override - public void writeSavableArrayListArray(ArrayList[] objects, String name, ArrayList[] defVal) throws IOException { - if (objects == null) {return;} - if (Arrays.equals(objects, defVal)) {return;} - - Element old = currentElement; - Element el = appendElement(name); - XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length)); - for (int i = 0; i < objects.length; i++) { - ArrayList o = objects[i]; - if(o == null){ - Element before = currentElement; - appendElement("null"); - currentElement = before; - }else{ - StringBuilder buf = new StringBuilder("SavableArrayList_"); - buf.append(i); - writeSavableArrayList(o, buf.toString(), null); - } + public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { + return; } - currentElement = old; + + int position = value.position(); + value.rewind(); + byte[] array = new byte[value.remaining()]; + value.get(array); + value.position(position); + + writePrimitiveArrayHelper(array, name); } @Override - public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; + public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { + return; + } - Element el = appendElement(name); - int size = value.length; - XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size)); + int position = value.position(); + value.rewind(); + short[] array = new short[value.remaining()]; + value.get(array); + value.position(position); - for (int i=0; i< size; i++) { - ArrayList[] vi = value[i]; - writeSavableArrayListArray(vi, "SavableArrayListArray_"+i, null); - } - currentElement = (Element) el.getParentNode(); + writePrimitiveArrayHelper(array, name); } @Override - public void writeFloatBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element el = appendElement(name); - XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); - for (FloatBuffer o : array) { - write(o, XMLExporter.ELEMENT_FLOATBUFFER, null); - } - currentElement = (Element) el.getParentNode(); + + int position = value.position(); + value.rewind(); + int[] array = new int[value.remaining()]; + value.get(array); + value.position(position); + + writePrimitiveArrayHelper(array, name); } @Override - public void writeSavableMap(Map map, String name, Map defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - Iterator keyIterator = map.keySet().iterator(); - while(keyIterator.hasNext()) { - Savable key = keyIterator.next(); - Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY); - write(key, XMLExporter.ELEMENT_KEY, null); - Savable value = map.get(key); - write(value, XMLExporter.ELEMENT_VALUE, null); - currentElement = stringMap; - } + int position = value.position(); + value.rewind(); + float[] array = new float[value.remaining()]; + value.get(array); + value.position(position); - currentElement = (Element) stringMap.getParentNode(); + writePrimitiveArrayHelper(array, name); } @Override - public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void writeByteBufferArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - Iterator keyIterator = map.keySet().iterator(); - while(keyIterator.hasNext()) { - String key = keyIterator.next(); - Element mapEntry = appendElement("MapEntry"); - XMLUtils.setAttribute(mapEntry, "key", key); - Savable s = map.get(key); - write(s, "Savable", null); - currentElement = stringMap; - } + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.size())); + for (int i = 0; i < value.size(); i++) { + String childName = "byte_buffer_" + i; + if (value.get(i) != null) { + write(value.get(i), childName, null); + } else { + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } + } - currentElement = (Element) stringMap.getParentNode(); + currentElement = (Element) currentElement.getParentNode(); } @Override - public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void writeFloatBufferArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - for(Entry entry : map) { - int key = entry.getKey(); - Element mapEntry = appendElement("MapEntry"); - XMLUtils.setAttribute(mapEntry, "key", Integer.toString(key)); - Savable s = entry.getValue(); - write(s, "Savable", null); - currentElement = stringMap; - } + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.size())); + for (int i = 0; i < value.size(); i++) { + String childName = "float_buffer_" + i; + if (value.get(i) != null) { + write(value.get(i), childName, null); + } else { + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } + } - currentElement = (Element) stringMap.getParentNode(); + currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { - if (value == null) { + public void writeSavableArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + Savable[] savableArray = new Savable[value.size()]; + for (int i = 0; i < value.size(); i++) { + Object o = value.get(i); + + if (o != null && !(o instanceof Savable)) { + throw new IOException(new ClassCastException("Not a Savable instance: " + o)); + } + + savableArray[i] = (Savable) o; } - - value.position(pos); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + write(savableArray, name, null); } @Override - public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { - if (value == null) { - return; - } - if (value.equals(defVal)) { + public void writeSavableArrayListArray(ArrayList[] value, String name, ArrayList[] defVal) throws IOException { + if (value == null || Arrays.equals(value, defVal)) { return; } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + Element old = currentElement; + + appendElement(name); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + String childName = "savable_list_" + i; + if(value[i] != null){ + writeSavableArrayList(value[i], childName, null); + }else{ + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } } - value.position(pos); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + currentElement = old; } @Override - public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { - if (value == null) return; - if (value.equals(defVal)) return; - - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); + public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException { + if (value == null || Arrays.equals(value, defVal)) { + return; } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + + Element old = currentElement; + + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + String childName = "savable_list_array_" + i; + if(value[i] != null){ + writeSavableArrayListArray(value[i], childName, null); + }else{ + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } } - - value.position(pos); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + currentElement = old; } @Override - public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { - if (value == null) { - return; - } - if (value.equals(defVal)) { + public void writeSavableMap(Map map, String name, Map defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + Element stringMap = appendElement(name); + + Iterator keyIterator = map.keySet().iterator(); + while(keyIterator.hasNext()) { + Savable key = keyIterator.next(); + Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY); + write(key, XMLExporter.ELEMENT_KEY, null); + Savable value = map.get(key); + write(value, XMLExporter.ELEMENT_VALUE, null); + currentElement = stringMap; } - - value.position(pos); - XMLUtils.setAttribute(el, dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + currentElement = (Element) stringMap.getParentNode(); } @Override - public void write(Enum value, String name, Enum defVal) throws IOException { - if (value == defVal) { + public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); + Element stringMap = appendElement(name); + + Iterator keyIterator = map.keySet().iterator(); + while(keyIterator.hasNext()) { + String key = keyIterator.next(); + Element mapEntry = appendElement("MapEntry"); + XMLUtils.setAttribute(mapEntry, "key", key); + Savable s = map.get(key); + write(s, "Savable", null); + currentElement = stringMap; } + currentElement = (Element) stringMap.getParentNode(); + } + @Override - public void writeByteBufferArrayList(ArrayList array, - String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - Element el = appendElement(name); - XMLUtils.setAttribute(el, "size", String.valueOf(array.size())); - for (ByteBuffer o : array) { - write(o, "ByteBuffer", null); - } - currentElement = (Element) el.getParentNode(); + Element stringMap = appendElement(name); + + for(Entry entry : map) { + int key = entry.getKey(); + Element mapEntry = appendElement("MapEntry"); + XMLUtils.setAttribute(mapEntry, "key", Integer.toString(key)); + Savable s = entry.getValue(); + write(s, "Savable", null); + currentElement = stringMap; } - + + currentElement = (Element) stringMap.getParentNode(); + } } \ No newline at end of file diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java index f0e664427b..af5f898bf8 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java @@ -46,7 +46,9 @@ * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book. * @author Doug Daniels (dougnukem) - adjustments for XML formatting * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $ + * @deprecated This class was only used in XMLExporter and has been replaced by javax.xml.transform.Transformer */ +@Deprecated public class DOMSerializer { /** The encoding to use for output (default is UTF-8) */ 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 add3f5ab70..bc4ef918b0 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 @@ -39,8 +39,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import org.w3c.dom.Document; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; /** * Part of the jME XML IO system as introduced in the Google Code jmexml project. @@ -53,27 +60,54 @@ public class XMLExporter implements JmeExporter { public static final String ELEMENT_MAPENTRY = "MapEntry"; public static final String ELEMENT_KEY = "Key"; public static final String ELEMENT_VALUE = "Value"; - public static final String ELEMENT_FLOATBUFFER = "FloatBuffer"; public static final String ATTRIBUTE_SIZE = "size"; + public static final String ATTRIBUTE_DATA = "data"; + public static final String ATTRIBUTE_CLASS = "class"; + public static final String ATTRIBUTE_REFERENCE_ID = "reference_ID"; + public static final String ATTRIBUTE_REFERENCE = "ref"; + public static final String ATTRIBUTE_SAVABLE_VERSIONS = "savable_versions"; private DOMOutputCapsule domOut; + + private int indentSpaces = 4; public XMLExporter() { } @Override - public void save(Savable object, OutputStream f) throws IOException { + public void save(Savable object, OutputStream outputStream) throws IOException { + Document document = null; try { - // Initialize the Document when saving, so we don't retain state of previous exports. - this.domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(), this); - domOut.write(object, object.getClass().getName(), null); - DOMSerializer serializer = new DOMSerializer(); - serializer.serialize(domOut.getDoc(), f); - f.flush(); + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); } catch (ParserConfigurationException ex) { throw new IOException(ex); } + document.setXmlStandalone(true); // for some reason the transformer output property below doesn't work unless the document is set to standalone + + // Initialize the DOMOutputCapsule when saving, so we don't retain state of previous exports. + domOut = new DOMOutputCapsule(document, this); + + domOut.write(object, "savable", null); + + DOMSource source = new DOMSource(domOut.getDoc()); + StreamResult result = new StreamResult(outputStream); + + try { + TransformerFactory tfFactory = TransformerFactory.newInstance(); + tfFactory.setAttribute("indent-number", indentSpaces); + + Transformer transformer = tfFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + + if (indentSpaces > 0) { + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + } + + transformer.transform(source, result); + } catch (TransformerException ex) { + throw new IOException(ex); + } } @Override @@ -96,8 +130,16 @@ public OutputCapsule getCapsule(Savable object) { return domOut; } + /** + * Sets the number of spaces used to indent nested tags. + * @param indentSpaces The number of spaces to indent for each level of nesting. Default is 4. + * Pass 0 to disable pretty printing and write document without adding any whitespace. + */ + public void setIndentSpaces(int indentSpaces) { + this.indentSpaces = indentSpaces; + } + public static XMLExporter getInstance() { - return new XMLExporter(); + return new XMLExporter(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java index 7cf2a98cb2..90d9e6e057 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java @@ -80,8 +80,9 @@ public Object load(AssetInfo info) throws IOException { try { return load(in); } finally { - if (in != null) + if (in != null) { in.close(); + } } } @@ -115,5 +116,4 @@ public InputCapsule getCapsule(Savable id) { public static XMLImporter getInstance() { return new XMLImporter(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java index 3dd8d9bbff..fcec673553 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java @@ -31,7 +31,9 @@ */ package com.jme3.export.xml; +import java.io.IOException; import org.w3c.dom.Element; +import org.w3c.dom.DOMException; /** * Utilities for reading and writing XML files. @@ -58,9 +60,16 @@ public class XMLUtils { * @param element element to set the attribute in * @param name name of the attribute (without prefix) * @param attribute attribute to save + * + * @throws java.io.IOException wraps DOMException in IOException for convenience since everywhere this method + * is used is expected to throw only IOException. */ - public static void setAttribute(Element element, String name, String attribute) { - element.setAttribute(PREFIX+name, attribute); + public static void setAttribute(Element element, String name, String attribute) throws IOException { + try { + element.setAttribute(PREFIX+name, attribute); + } catch (DOMException domEx) { + throw new IOException(domEx); + } } /** @@ -106,5 +115,4 @@ public static boolean hasAttribute(int version, Element element, String name) { */ private XMLUtils() { } - } From 5d9d53b75015431e64dc25a26e5dbf5fc2327445 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 25 Oct 2024 09:30:32 -0700 Subject: [PATCH 54/57] update README.md to report 3.7 as the latest stable version (#2322) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aab2582e50..0a79591c95 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ jMonkeyEngine [![Build Status](https://github.com/jMonkeyEngine/jmonkeyengine/workflows/Build%20jMonkeyEngine/badge.svg)](https://github.com/jMonkeyEngine/jmonkeyengine/actions) jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. -v3.6.1 is the latest stable version of the engine. +v3.7.0 is the latest stable version of the engine. The engine is used by several commercial game studios and computer-science courses. Here's a taste: From b0938f0b3b8c13cc3f663666637b58cabf73a5b6 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 25 Oct 2024 09:38:40 -0700 Subject: [PATCH 55/57] Armature.java: javadoc correction (to solve issue #2142 again) (#2315) --- jme3-core/src/main/java/com/jme3/anim/Armature.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 5058fcec24..197242cfa1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -111,7 +111,7 @@ private void createSkinningMatrices() { /** * Sets the JointModelTransform implementation - * Default is {@link MatrixJointModelTransform} + * Default is {@link SeparateJointModelTransform} * * @param modelTransformClass which implementation to use * @see JointModelTransform From b711c776eb96fa47dacdcf5ca2a839077b99b9cb Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 25 Oct 2024 21:13:26 -0700 Subject: [PATCH 56/57] update j-ogg-vorbis to v1.0.6 (#2318) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b21814aee0..3a1e4a7a67 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ gradle-git = "org.ajoberstar:gradle-git:1.2.0" gradle-retrolambda = "me.tatarka:gradle-retrolambda:3.7.1" groovy-test = "org.codehaus.groovy:groovy-test:3.0.22" gson = "com.google.code.gson:gson:2.9.1" -j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.4" +j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.6" jbullet = "com.github.stephengold:jbullet:1.0.3" jinput = "net.java.jinput:jinput:2.0.9" jna = "net.java.dev.jna:jna:5.10.0" From deef7489a0d4cb13b50fd105214cfdd58b479686 Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Sun, 27 Oct 2024 19:27:46 +0100 Subject: [PATCH 57/57] AnimComposer: return AnimLayer on the makeLayer and removeLayer methods. (#2231) * AnimComposer: return AnimLayer on the makeLayer and removeLayer methods. * Update License year * repair my failed attempt to resolve the merge conflict --------- Co-authored-by: Stephen Gold --- .../src/main/java/com/jme3/anim/AnimComposer.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index f9cdf81167..f9fd84edbf 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -310,21 +310,24 @@ public Action removeAction(String name) { /** * Add a layer to this composer. * - * @param name the desired name for the new layer - * @param mask the desired mask for the new layer (alias created) + * @param name The desired name for the new layer + * @param mask The desired mask for the new layer (alias created) + * @return a new layer */ - public void makeLayer(String name, AnimationMask mask) { + public AnimLayer makeLayer(String name, AnimationMask mask) { AnimLayer l = new AnimLayer(name, mask); layers.put(name, l); + return l; } /** * Remove specified layer. This will stop the current action on this layer. * * @param name The name of the layer to remove. + * @return The removed layer. */ - public void removeLayer(String name) { - layers.remove(name); + public AnimLayer removeLayer(String name) { + return layers.remove(name); } /**