From 1ba207197973dacb79cc22b278f634d3d06823e0 Mon Sep 17 00:00:00 2001 From: Scrappers Team <60224159+Scrappers-glitch@users.noreply.github.com> Date: Mon, 23 May 2022 17:57:13 +0200 Subject: [PATCH 001/140] Android: Implemented AndroidNativeBufferAllocator - Deprecated AndroidBufferAllocator (#1821) --- jme3-android-native/bufferallocator.gradle | 59 +++++++++++ jme3-android-native/build.gradle | 1 + .../src/native/jme_bufferallocator/Android.mk | 50 ++++++++++ .../native/jme_bufferallocator/Application.mk | 39 ++++++++ ...m_jme3_util_AndroidNativeBufferAllocator.c | 99 +++++++++++++++++++ .../com/jme3/system/android/OGLESContext.java | 4 +- .../com/jme3/util/AndroidBufferAllocator.java | 4 +- .../util/AndroidNativeBufferAllocator.java | 74 ++++++++++++++ 8 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 jme3-android-native/bufferallocator.gradle create mode 100644 jme3-android-native/src/native/jme_bufferallocator/Android.mk create mode 100644 jme3-android-native/src/native/jme_bufferallocator/Application.mk create mode 100644 jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c create mode 100644 jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java diff --git a/jme3-android-native/bufferallocator.gradle b/jme3-android-native/bufferallocator.gradle new file mode 100644 index 00000000000..f03027e2753 --- /dev/null +++ b/jme3-android-native/bufferallocator.gradle @@ -0,0 +1,59 @@ +// build file for native buffer allocator, created by pavl_g on 5/17/22. + +// directories for native source +String bufferAllocatorAndroidPath = 'src/native/jme_bufferallocator' +String bufferAllocatorHeaders = 'src/native/headers' + +//Pre-compiled libs directory +def rootPath = rootProject.projectDir.absolutePath +String bufferAllocatorPreCompiledLibsDir = + rootPath + File.separator + "build" + File.separator + 'native' + File.separator + 'android' + File.separator + 'allocator' + +// directories for build +String bufferAllocatorBuildDir = "$buildDir" + File.separator + "bufferallocator" +String bufferAllocatorJniDir = bufferAllocatorBuildDir + File.separator + "jni" +String bufferAllocatorHeadersBuildDir = bufferAllocatorJniDir + File.separator + "headers" +String bufferAllocatorBuildLibsDir = bufferAllocatorBuildDir + File.separator + "libs" + +// copy native src to build dir +task copyJmeBufferAllocator(type: Copy) { + from file(bufferAllocatorAndroidPath) + into file(bufferAllocatorJniDir) +} + +// copy native headers to build dir +task copyJmeHeadersBufferAllocator(type: Copy, dependsOn: copyJmeBufferAllocator) { + from file(bufferAllocatorHeaders) + into file(bufferAllocatorHeadersBuildDir) +} + +// compile and build copied natives in build dir +task buildBufferAllocatorNativeLib(type: Exec, dependsOn: [copyJmeBufferAllocator, copyJmeHeadersBufferAllocator]) { + workingDir bufferAllocatorBuildDir + executable rootProject.ndkCommandPath + args "-j" + Runtime.runtime.availableProcessors() +} + +task updatePreCompiledLibsBufferAllocator(type: Copy, dependsOn: buildBufferAllocatorNativeLib) { + from file(bufferAllocatorBuildLibsDir) + into file(bufferAllocatorPreCompiledLibsDir) +} + +// Copy pre-compiled libs to build directory (when not building new libs) +task copyPreCompiledLibsBufferAllocator(type: Copy) { + from file(bufferAllocatorPreCompiledLibsDir) + into file(bufferAllocatorBuildLibsDir) +} + +// ndkExists is a boolean from the build.gradle in the root project +// buildNativeProjects is a string set to "true" +if (ndkExists && buildNativeProjects == "true") { + // build native libs and update stored pre-compiled libs to commit + compileJava.dependsOn { updatePreCompiledLibsBufferAllocator } +} else { + // use pre-compiled native libs (not building new ones) + compileJava.dependsOn { copyPreCompiledLibsBufferAllocator } +} + +// package the native object files inside the lib folder in a production jar +jar.into("lib") { from bufferAllocatorBuildLibsDir } diff --git a/jme3-android-native/build.gradle b/jme3-android-native/build.gradle index d0618bb1eaf..cc78ab4722c 100644 --- a/jme3-android-native/build.gradle +++ b/jme3-android-native/build.gradle @@ -35,3 +35,4 @@ 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') diff --git a/jme3-android-native/src/native/jme_bufferallocator/Android.mk b/jme3-android-native/src/native/jme_bufferallocator/Android.mk new file mode 100644 index 00000000000..d735478fb6c --- /dev/null +++ b/jme3-android-native/src/native/jme_bufferallocator/Android.mk @@ -0,0 +1,50 @@ +# +# Copyright (c) 2009-2022 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. + +## +# Created by pavl_g on 5/17/22. +# For more : https://developer.android.com/ndk/guides/android_mk. +## +TARGET_PLATFORM := android-19 + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_LDLIBS := -llog -Wl,-s + +LOCAL_MODULE := bufferallocatorjme + +LOCAL_C_INCLUDES := $(LOCAL_PATH) + +LOCAL_SRC_FILES := com_jme3_util_AndroidNativeBufferAllocator.c + +include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-android-native/src/native/jme_bufferallocator/Application.mk b/jme3-android-native/src/native/jme_bufferallocator/Application.mk new file mode 100644 index 00000000000..4bcc2ef85e6 --- /dev/null +++ b/jme3-android-native/src/native/jme_bufferallocator/Application.mk @@ -0,0 +1,39 @@ +# +# Copyright (c) 2009-2022 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. + +## +# Created by pavl_g on 5/17/22. +# For more : https://developer.android.com/ndk/guides/application_mk. +## +APP_PLATFORM := android-19 +# change this to 'debug' to see android logs +APP_OPTIM := release +APP_ABI := all \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c b/jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c new file mode 100644 index 00000000000..4f5cd66d09a --- /dev/null +++ b/jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2022 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. + */ + +/** + * @file com_jme3_util_AndroidNativeBufferAllocator.c + * @author pavl_g. + * @brief Creates and releases direct byte buffers for {com.jme3.util.AndroidNativeBufferAllocator}. + * @date 2022-05-17. + * @note + * Find more at : + * - JNI Direct byte buffers : https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewDirectByteBuffer. + * - JNI Get Direct byte buffer : https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetDirectBufferAddress. + * - GNU Basic allocation : https://www.gnu.org/software/libc/manual/html_node/Basic-Allocation.html. + * - GNU Allocating Cleared Space : https://www.gnu.org/software/libc/manual/html_node/Allocating-Cleared-Space.html. + * - GNU No Memory error : https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html#index-ENOMEM. + * - GNU Freeing memory : https://www.gnu.org/software/libc/manual/html_node/Freeing-after-Malloc.html. + * - Android logging : https://developer.android.com/ndk/reference/group/logging. + * - Android logging example : https://github.com/android/ndk-samples/blob/7a8ff4c5529fce6ec4c5796efbe773f5d0e569cc/hello-libs/app/src/main/cpp/hello-libs.cpp#L25-L26. + */ + +#include "headers/com_jme3_util_AndroidNativeBufferAllocator.h" +#include +#include +#include + +#ifndef NDEBUG +#include +#define LOG(LOG_ID, ...) __android_log_print(LOG_ID, \ + "AndroidNativeBufferAllocator", ##__VA_ARGS__); +#else +#define LOG(...) +#endif + +bool isDeviceOutOfMemory(void*); + +/** + * @brief Tests if the device is out of memory. + * + * @return true if the buffer to allocate is a NULL pointer and the errno is ENOMEM (Error-no-memory). + * @return false otherwise. + */ +bool isDeviceOutOfMemory(void* buffer) { + return buffer == NULL && errno == ENOMEM; +} + +JNIEXPORT void JNICALL Java_com_jme3_util_AndroidNativeBufferAllocator_releaseDirectByteBuffer +(JNIEnv * env, jobject object, jobject bufferObject) +{ + void* buffer = (*env)->GetDirectBufferAddress(env, bufferObject); + // deallocates the buffer pointer + free(buffer); + // log the destruction by mem address + LOG(ANDROID_LOG_INFO, "Buffer released (mem_address, size) -> (%p, %lu)", buffer, sizeof(buffer)); + // avoid accessing this memory space by resetting the memory address + buffer = NULL; + LOG(ANDROID_LOG_INFO, "Buffer mem_address formatted (mem_address, size) -> (%p, %u)", buffer, sizeof(buffer)); +} + +JNIEXPORT jobject JNICALL Java_com_jme3_util_AndroidNativeBufferAllocator_createDirectByteBuffer +(JNIEnv * env, jobject object, jlong size) +{ + void* buffer = calloc(1, size); + if (isDeviceOutOfMemory(buffer)) { + LOG(ANDROID_LOG_FATAL, "Device is out of memory exiting with %u", errno); + exit(errno); + } else { + LOG(ANDROID_LOG_INFO, "Buffer created successfully (mem_address, size) -> (%p %lli)", buffer, size); + } + return (*env)->NewDirectByteBuffer(env, buffer, size); +} \ No newline at end of file diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index 6d342381005..a80eb7cccd6 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -54,8 +54,8 @@ import com.jme3.renderer.android.AndroidGL; import com.jme3.renderer.opengl.*; import com.jme3.system.*; -import com.jme3.util.AndroidBufferAllocator; import com.jme3.util.BufferAllocatorFactory; +import com.jme3.util.AndroidNativeBufferAllocator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -82,7 +82,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION; if (System.getProperty(implementation) == null) { - System.setProperty(implementation, AndroidBufferAllocator.class.getName()); + System.setProperty(implementation, AndroidNativeBufferAllocator.class.getName()); } } diff --git a/jme3-android/src/main/java/com/jme3/util/AndroidBufferAllocator.java b/jme3-android/src/main/java/com/jme3/util/AndroidBufferAllocator.java index 596bafc138a..6ddc00f17ee 100644 --- a/jme3-android/src/main/java/com/jme3/util/AndroidBufferAllocator.java +++ b/jme3-android/src/main/java/com/jme3/util/AndroidBufferAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,9 @@ /** * @author Jesus Oliver + * @deprecated implemented {@link AndroidNativeBufferAllocator} instead. */ +@Deprecated public class AndroidBufferAllocator implements BufferAllocator { // We make use of the ReflectionAllocator to remove the inner buffer diff --git a/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java b/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java new file mode 100644 index 00000000000..f91334db7ed --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2009-2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.nio.Buffer; +import java.nio.ByteBuffer; + +/** + * Allocates and destroys direct byte buffers using native code. + * + * @author pavl_g. + */ +public final class AndroidNativeBufferAllocator implements BufferAllocator { + + static { + System.loadLibrary("bufferallocatorjme"); + } + + @Override + public void destroyDirectBuffer(Buffer toBeDestroyed) { + releaseDirectByteBuffer(toBeDestroyed); + } + + @Override + public ByteBuffer allocate(int size) { + return createDirectByteBuffer(size); + } + + /** + * Releases the memory of a direct buffer using a buffer object reference. + * + * @param buffer the buffer reference to release its memory. + * @see AndroidNativeBufferAllocator#destroyDirectBuffer(Buffer) + */ + private native void releaseDirectByteBuffer(Buffer buffer); + + /** + * Creates a new direct byte buffer explicitly with a specific size. + * + * @param size the byte buffer size used for allocating the buffer. + * @return a new direct byte buffer object. + * @see AndroidNativeBufferAllocator#allocate(int) + */ + private native ByteBuffer createDirectByteBuffer(long size); +} \ No newline at end of file From 70244d990ee3a4df6854c78cdfa48ec3d8914a4b Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 23 May 2022 16:17:13 +0000 Subject: [PATCH 002/140] [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 1d83fcfd579..420ae3baa11 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=ec136bac246ac6ff21426b227076541ce036555a +natives.snapshot=1ba207197973dacb79cc22b278f634d3d06823e0 From 488d9fa1e9a9a88d9b73dd9717292c78c7ef52d7 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Tue, 24 May 2022 10:25:49 +0200 Subject: [PATCH 003/140] Add GL debug capabilities (#1790) * Add GL debug capabilities * Fix: check for null names --- .../src/main/java/com/jme3/renderer/Caps.java | 7 +++- .../java/com/jme3/renderer/RenderManager.java | 11 ++++- .../main/java/com/jme3/renderer/Renderer.java | 9 ++++ .../java/com/jme3/renderer/opengl/GL3.java | 5 +++ .../java/com/jme3/renderer/opengl/GLExt.java | 24 +++++++++++ .../com/jme3/renderer/opengl/GLRenderer.java | 42 +++++++++++++++++++ .../java/com/jme3/scene/VertexBuffer.java | 12 ++++++ .../java/com/jme3/system/AppSettings.java | 6 ++- .../java/com/jme3/texture/FrameBuffer.java | 8 ++++ .../com/jme3/renderer/lwjgl/LwjglGLExt.java | 16 +++++++ .../com/jme3/system/lwjgl/LwjglContext.java | 1 + 11 files changed, 138 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index 89ecf4e17ff..961d78ae6f2 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -438,7 +438,12 @@ public enum Caps { /** * Supports unpack row length (stride). */ - UnpackRowLength + UnpackRowLength, + + /** + * Supports debugging capabilities + */ + GLDebug ; /** 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 fa97f386264..a1b453a096d 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -622,6 +622,7 @@ public void updateUniformBindings(Shader shader) { * @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) */ public void renderGeometry(Geometry geom) { + this.renderer.pushDebugGroup(geom.getName()); if (geom.isIgnoreTransform()) { setWorldMatrix(Matrix4f.IDENTITY); } else { @@ -680,6 +681,7 @@ public void renderGeometry(Geometry geom) { } else { material.render(geom, lightList, this); } + this.renderer.popDebugGroup(); } /** @@ -955,7 +957,9 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { if (prof != null) { prof.vpStep(VpStep.RenderBucket, vp, Bucket.Opaque); } + this.renderer.pushDebugGroup(Bucket.Opaque.name()); rq.renderQueue(Bucket.Opaque, this, cam, flush); + this.renderer.popDebugGroup(); // render the sky, with depth range set to the farthest if (!rq.isQueueEmpty(Bucket.Sky)) { @@ -963,7 +967,9 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { prof.vpStep(VpStep.RenderBucket, vp, Bucket.Sky); } renderer.setDepthRange(1, 1); + this.renderer.pushDebugGroup(Bucket.Sky.name()); rq.renderQueue(Bucket.Sky, this, cam, flush); + this.renderer.popDebugGroup(); depthRangeChanged = true; } @@ -979,8 +985,9 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { renderer.setDepthRange(0, 1); depthRangeChanged = false; } - + this.renderer.pushDebugGroup(Bucket.Transparent.name()); rq.renderQueue(Bucket.Transparent, this, cam, flush); + this.renderer.popDebugGroup(); } if (!rq.isQueueEmpty(Bucket.Gui)) { @@ -989,7 +996,9 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { } renderer.setDepthRange(0, 0); setCamera(cam, true); + this.renderer.pushDebugGroup(Bucket.Gui.name()); rq.renderQueue(Bucket.Gui, this, cam, flush); + this.renderer.popDebugGroup(); setCamera(cam, false); depthRangeChanged = true; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index f6deaa165e9..d25e5147817 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -519,4 +519,13 @@ public void setTexture(int unit, Texture tex) * @return true for conversion, false for no conversion */ public boolean isMainFrameBufferSrgb(); + + public default void popDebugGroup() { + + } + + public default void pushDebugGroup(String name) { + + } + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java index 0ecc8432e53..cf8aeb790f8 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java @@ -127,6 +127,11 @@ public interface GL3 extends GL2 { */ public static final int GL_TRANSFORM_FEEDBACK_BUFFER = 0x8C8E; + + public static final int GL_FRAMEBUFFER = 0x8D40; + public static final int GL_READ_FRAMEBUFFER = 0x8CA8; + public static final int GL_DRAW_FRAMEBUFFER = 0x8CA9; + /** *

Reference Page

*

diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java index 39184475e1c..ea9add1583e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java @@ -108,6 +108,21 @@ public interface GLExt { public static final int GL_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C; public static final int GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D; + public static final int GL_DEBUG_SOURCE_API = 0x8246; + public static final int GL_DEBUG_SOURCE_WINDOW_SYSTEM = 0x8247; + public static final int GL_DEBUG_SOURCE_SHADER_COMPILER = 0x8248; + public static final int GL_DEBUG_SOURCE_THIRD_PARTY = 0x8249; + public static final int GL_DEBUG_SOURCE_APPLICATION = 0x824A; + public static final int GL_DEBUG_SOURCE_OTHER = 0x824B; + + public static final int GL_BUFFER = 0x82E0; + public static final int GL_SHADER = 0x82E1; + public static final int GL_PROGRAM = 0x82E2; + public static final int GL_QUERY = 0x82E3; + public static final int GL_PROGRAM_PIPELINE = 0x82E4; + public static final int GL_SAMPLER = 0x82E6; + public static final int GL_DISPLAY_LIST = 0x82E7; + /** *

Reference Page

* @@ -239,4 +254,13 @@ public void glTexImage2DMultisample(int target, int samples, int internalFormat, * @param divisor the divisor value. */ public void glVertexAttribDivisorARB(int index, int divisor); + + public default void glPushDebugGroup(int source, int id, String message) { + } + + public default void glPopDebugGroup() { + } + + public default void glObjectLabel(int identifier, int id, String label){ + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 91ba96d9bb2..6a6ada666c7 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -109,6 +109,9 @@ public final class GLRenderer implements Renderer { private final GLExt glext; private final GLFbo glfbo; private final TextureUtil texUtil; + private boolean debug = false; + private int debugGroupId = 0; + public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) { this.gl = gl; @@ -128,6 +131,29 @@ public void setGenerateMipmapsForFrameBuffer(boolean v) { generateMipmapsForFramebuffers = v; } + public void setDebugEnabled(boolean v) { + debug = v; + } + + @Override + public void popDebugGroup() { + if (debug && caps.contains(Caps.GLDebug)) { + glext.glPopDebugGroup(); + debugGroupId--; + } + } + + @Override + public void pushDebugGroup(String name) { + if (debug && caps.contains(Caps.GLDebug)) { + if (name == null) name = "Group " + debugGroupId; + glext.glPushDebugGroup(GLExt.GL_DEBUG_SOURCE_APPLICATION, debugGroupId, name); + debugGroupId++; + } + } + + + @Override public Statistics getStatistics() { return statistics; @@ -574,6 +600,10 @@ private void loadCapabilitiesCommon() { caps.add(Caps.UnpackRowLength); } + if (caps.contains(Caps.OpenGL43) || hasExtension("GL_KHR_debug")) { + caps.add(Caps.GLDebug); + } + // Print context information logger.log(Level.INFO, "OpenGL Renderer Information\n" + " * Vendor: {0}\n" + @@ -1431,6 +1461,9 @@ public void updateShaderSourceData(ShaderSource source) { } source.setId(id); + if (debug && caps.contains(Caps.GLDebug)) { + if(source.getName() != null) glext.glObjectLabel(GLExt.GL_SHADER, id, source.getName()); + } } else { throw new RendererException("Cannot recompile shader source"); } @@ -2103,6 +2136,9 @@ public void setFrameBuffer(FrameBuffer fb) { assert context.boundFBO == fb.getId(); context.boundFB = fb; + if (debug && caps.contains(Caps.GLDebug)) { + if (fb.getName() != null) glext.glObjectLabel(GL3.GL_FRAMEBUFFER, fb.getId(), fb.getName()); + } } } @@ -2626,6 +2662,9 @@ public void setTexture(int unit, Texture tex) throws TextureUnitException { assert texId != -1; setupTextureParams(unit, tex); + if (debug && caps.contains(Caps.GLDebug)) { + if (tex.getName() != null) glext.glObjectLabel(GL.GL_TEXTURE, tex.getImage().getId(), tex.getName()); + } } @@ -2995,6 +3034,9 @@ public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { attribs[slot] = vb.getWeakRef(); } } + if (debug && caps.contains(Caps.GLDebug)) { + if (vb.getName() != null) glext.glObjectLabel(GLExt.GL_BUFFER, vb.getId(), vb.getName()); + } } public void setVertexAttrib(VertexBuffer vb) { diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java index 36b94137e8d..8698bf421a2 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -332,6 +332,7 @@ public int getComponentSize() { protected boolean normalized = false; protected int instanceSpan = 0; protected transient boolean dataSizeChanged = false; + protected String name; /** * Creates an empty, uninitialized buffer. @@ -1189,4 +1190,15 @@ public void read(JmeImporter im) throws IOException { throw new IOException("Unsupported import buffer format: " + format); } } + + public String getName() { + if (name == null) { + name = getClass().getSimpleName() + "(" + getBufferType().name() + ")"; + } + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index cdc1324ea9b..f3fb5a158ff 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -1299,7 +1299,9 @@ public String getOpenCLPlatformChooser() { * Determine if the renderer will be run in Graphics Debug mode, which means every openGL call is checked and * if it returns an error code, throw a {@link com.jme3.renderer.RendererException}.
* Without this, many openGL calls might fail without notice, so turning it on is recommended for development. - * + * Graphics Debug mode will also label native objects and group calls on supported renderers. Compatible + * graphics debuggers will be able to use this data to show a better outlook of your application + * * @return whether the context will be run in Graphics Debug Mode or not * @see #setGraphicsDebug(boolean) */ @@ -1311,6 +1313,8 @@ public boolean isGraphicsDebug() { * Set whether the renderer will be run in Graphics Debug mode, which means every openGL call is checked and * if it returns an error code, throw a {@link com.jme3.renderer.RendererException}.
* Without this, many openGL calls might fail without notice, so turning it on is recommended for development. + * Graphics Debug mode will also label native objects and group calls on supported renderers. Compatible + * graphics debuggers will be able to use this data to show a better outlook of your application * * @param debug whether the context will be run in Graphics Debug Mode or not * @see #isGraphicsDebug() 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 1b66f9bee7b..3b18bce1a25 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -84,6 +84,7 @@ public class FrameBuffer extends NativeObject { private RenderBuffer depthBuf = null; private int colorBufIndex = 0; private boolean srgb; + private String name; private Boolean mipMapsGenerationHint = null; /** @@ -844,6 +845,13 @@ public boolean isSrgb() { return srgb; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } /** * Hints the renderer to generate mipmaps for this framebuffer if necessary diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java index 71183382294..f9899fe8b39 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java @@ -102,4 +102,20 @@ public int glClientWaitSync(final Object sync, final int flags, final long timeo public void glDeleteSync(final Object sync) { ARBSync.glDeleteSync((Long) sync); } + + @Override + public void glPushDebugGroup(int source, int id, String message) { + KHRDebug.glPushDebugGroup(source, id, message); + } + + @Override + public void glPopDebugGroup() { + KHRDebug.glPopDebugGroup(); + } + + @Override + public void glObjectLabel(int identifier, int id, String label) { + assert label != null; + KHRDebug.glObjectLabel(identifier, id, label); + } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index baa28243d09..84aa6e387d6 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -237,6 +237,7 @@ private void initContext(boolean first) { } this.renderer = new GLRenderer(gl, glext, glfbo); + if (this.settings.isGraphicsDebug()) ((GLRenderer)this.renderer).setDebugEnabled(true); } this.renderer.initialize(); From 97e83fbee30cf2b78bb1617ab17a5b6167ae29f4 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Tue, 24 May 2022 11:43:23 +0200 Subject: [PATCH 004/140] Add java types to VarType and type checks to MatParam (#1797) --- .../main/java/com/jme3/material/MatParam.java | 37 ++++++++ .../main/java/com/jme3/shader/VarType.java | 86 +++++++++++++------ 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java index 3bd50332fcf..35111331bed 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParam.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java @@ -39,6 +39,7 @@ import com.jme3.texture.Texture.WrapMode; import java.io.IOException; +import java.util.Arrays; /** * Describes a material parameter. This is used for both defining a name and type @@ -52,6 +53,7 @@ public class MatParam implements Savable, Cloneable { protected String name; protected String prefixedName; protected Object value; + protected boolean typeCheck = true; /** * Create a new material parameter. For internal use only. @@ -73,6 +75,22 @@ public MatParam(VarType type, String name, Object value) { protected MatParam() { } + + public boolean isTypeCheckEnabled() { + return typeCheck; + } + + + /** + * Enable type check for this param. + * When type check is enabled a RuntimeException is thrown if + * an object of the wrong type is passed to setValue. + * @param v (default = true) + */ + public void setTypeCheckEnabled(boolean v) { + typeCheck = v; + } + /** * Returns the material parameter type. * @@ -130,6 +148,21 @@ public Object getValue() { * @param value the value of this material parameter. */ public void setValue(Object value) { + if (isTypeCheckEnabled()) { + if (value != null && this.type != null && this.type.getJavaType().length != 0) { + boolean valid = false; + for (Class jtype : this.type.getJavaType()) { + if (jtype.isAssignableFrom(value.getClass())) { + valid = true; + break; + } + } + if (!valid) { + throw new RuntimeException("Trying to assign a value of type " + value.getClass() + " to " + this.getName() + " of type " + type.name() + ". Valid types are " + + Arrays.deepToString(type.getJavaType())); + } + } + } this.value = value; } @@ -323,6 +356,8 @@ public void write(JmeExporter ex) throws IOException { } else if (value.getClass().isArray() && value instanceof Savable[]) { oc.write((Savable[]) value, "value_savable_array", null); } + + oc.write(typeCheck, "typeCheck", true); } @Override @@ -380,6 +415,8 @@ public void read(JmeImporter im) throws IOException { value = ic.readSavable("value_savable", null); break; } + + typeCheck = ic.readBoolean("typeCheck", true); } @Override diff --git a/jme3-core/src/main/java/com/jme3/shader/VarType.java b/jme3-core/src/main/java/com/jme3/shader/VarType.java index 877f47d0bbe..3a1ca47fa27 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VarType.java +++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java @@ -30,49 +30,85 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.shader; - +import com.jme3.math.*; +import com.jme3.texture.*; +import com.jme3.shader.BufferObject; public enum VarType { - Float("float"), - Vector2("vec2"), - Vector3("vec3"), - Vector4("vec4"), + Float("float",float.class,Float.class), + Vector2("vec2",Vector2f.class), + Vector3("vec3",Vector3f.class), + Vector4("vec4",Vector4f.class, ColorRGBA.class), - IntArray(true,false,"int"), - FloatArray(true,false,"float"), - Vector2Array(true,false,"vec2"), - Vector3Array(true,false,"vec3"), - Vector4Array(true,false,"vec4"), + IntArray(true,false,"int",int[].class,Integer[].class), + FloatArray(true,false,"float",float[].class,Float[].class), + Vector2Array(true,false,"vec2",Vector2f[].class), + Vector3Array(true,false,"vec3",Vector3f[].class), + Vector4Array(true,false,"vec4",Vector4f[].class), - Boolean("bool"), + Boolean("bool",Boolean.class,boolean.class), - Matrix3(true,false,"mat3"), - Matrix4(true,false,"mat4"), + Matrix3(true,false,"mat3",Matrix3f.class), + Matrix4(true,false,"mat4",Matrix4f.class), - Matrix3Array(true,false,"mat3"), - Matrix4Array(true,false,"mat4"), + Matrix3Array(true,false,"mat3",Matrix3f[].class), + Matrix4Array(true,false,"mat4",Matrix4f[].class), - TextureBuffer(false,true,"sampler1D|sampler1DShadow"), - Texture2D(false,true,"sampler2D|sampler2DShadow"), - Texture3D(false,true,"sampler3D"), - TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow"), - TextureCubeMap(false,true,"samplerCube"), - Int("int"), - BufferObject(false, false, "custom"); + TextureBuffer(false,true,"sampler1D|sampler1DShadow"), + Texture2D(false,true,"sampler2D|sampler2DShadow",Texture2D.class,Texture.class), + Texture3D(false,true,"sampler3D",Texture3D.class,Texture.class), + TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow",TextureArray.class,Texture.class), + TextureCubeMap(false,true,"samplerCube",TextureCubeMap.class,Texture.class), + Int("int",int.class,Integer.class), + BufferObject(false, false, "custom", BufferObject.class); + private boolean usesMultiData = false; private boolean textureType = false; final private String glslType; - + private Class javaTypes[]; - VarType(String glslType){ + VarType(String glslType,Class ...javaTypes){ this.glslType = glslType; + if (javaTypes != null) { + this.javaTypes = javaTypes; + } else { + this.javaTypes = new Class[0]; + } + } - VarType(boolean multiData, boolean textureType,String glslType){ + + VarType(boolean multiData, boolean textureType, String glslType, Class... javaTypes) { usesMultiData = multiData; this.textureType = textureType; this.glslType = glslType; + if (javaTypes != null) { + this.javaTypes = javaTypes; + } else { + this.javaTypes = new Class[0]; + } + } + + /** + * Check if the passed object is of a type mapped to this VarType + * @param o Object to check + * @return true if the object type is mapped to this VarType + */ + public boolean isOfType(Object o){ + for(Class c : javaTypes){ + if(c.isAssignableFrom(o.getClass()))return true; + } + return false; + } + + + /** + * Get the java types mapped to this VarType + * @return an array of classes mapped to this VarType + */ + public Class[] getJavaType(){ + return javaTypes; } public boolean isTextureType() { From 25f9ebb51fa5e87af056d6e1d6e6528c0d784378 Mon Sep 17 00:00:00 2001 From: Scrappers Date: Mon, 30 May 2022 16:40:40 +0200 Subject: [PATCH 005/140] SettingsDialog: Fixed LAF --- jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java index 003ea21bca2..271aeeb9d60 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java @@ -297,12 +297,6 @@ private void createUI() { GridBagConstraints gbc; JPanel mainPanel = new JPanel(new GridBagLayout()); - - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - logger.warning("Could not set native look and feel."); - } addWindowListener(new WindowAdapter() { From a257160de4d7e8b77349fb7e4cbdb7c47b37a16b Mon Sep 17 00:00:00 2001 From: Scrappers Date: Mon, 30 May 2022 16:41:23 +0200 Subject: [PATCH 006/140] SettingsDialog: Updated jme3 copyright --- jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java index 271aeeb9d60..1198ecd4f47 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without From af375391ea385a061444ca03262e1a4720c3274d Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 1 Jul 2022 12:47:48 -0700 Subject: [PATCH 007/140] README.md: add a link to Chatter Games website --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6eb72e41792..f023bc95ca2 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ The engine is used by several commercial game studios and computer-science cours - [Jumping Jack Flag](http://timealias.bplaced.net/jack/) - [PapaSpace Flight Simulation](https://www.papaspace.at/) - [Cubic Nightmare](https://jaredbgreat.itch.io/cubic-nightmare) + - [Chatter Games](https://chatter-games.com) ## Getting started From 16a1ffa34a23a851e51d051cf4f0b3cf3a8426a8 Mon Sep 17 00:00:00 2001 From: Florian Frankenberger Date: Mon, 25 Jul 2022 15:30:25 +0200 Subject: [PATCH 008/140] Update README.md to include Exotic Matter (#1838) Would be great to have our game Exotic Matter also mentioned as its engine is based on jME. Thanks! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f023bc95ca2..4864152c2ea 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The engine is used by several commercial game studios and computer-science cours - [PapaSpace Flight Simulation](https://www.papaspace.at/) - [Cubic Nightmare](https://jaredbgreat.itch.io/cubic-nightmare) - [Chatter Games](https://chatter-games.com) + - [Exotic Matter](https://exoticmatter.io) ## Getting started From de5c87354b7182ab97e1b9238486ed3841725766 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Mon, 25 Jul 2022 12:36:10 -0700 Subject: [PATCH 009/140] jme3-examples: update the fallback URLs for "TerrainGridTestData.zip" --- .../java/jme3test/terrain/TerrainGridSerializationTest.java | 4 +++- .../main/java/jme3test/terrain/TerrainGridTileLoaderTest.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java index 23aa76f3bff..d380201516f 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java @@ -38,7 +38,9 @@ public static void main(final String[] args) { public void simpleInitApp() { File file = new File("TerrainGridTestData.zip"); if (!file.exists()) { - assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class); + assetManager.registerLocator( + "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/TerrainGridTestData.zip", + HttpZipLocator.class); } else { assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class); } diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java index 69bc1ba7577..7cf0eb82f96 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java @@ -48,7 +48,9 @@ public static void main(final String[] args) { public void simpleInitApp() { File file = new File("TerrainGridTestData.zip"); if (!file.exists()) { - assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class); + assetManager.registerLocator( + "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/TerrainGridTestData.zip", + HttpZipLocator.class); } else { assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class); } From 43e9302a5bd6dfbdbc0399033ca594444a11a4ce Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Wed, 27 Jul 2022 17:24:33 +0200 Subject: [PATCH 010/140] Improved code readability: ParticlePointMesh and ParticleTriMesh (#1831) --- .../java/com/jme3/effect/ParticlePointMesh.java | 2 ++ .../java/com/jme3/effect/ParticleTriMesh.java | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java b/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java index 10f7ace1b14..8f43bec5b22 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java @@ -133,6 +133,7 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers colors.rewind(); sizes.rewind(); texcoords.rewind(); + for (int i = 0; i < particles.length; i++) { Particle p = particles[i]; @@ -153,6 +154,7 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers texcoords.put(startX).put(startY).put(endX).put(endY); } + positions.flip(); colors.flip(); sizes.flip(); diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java b/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java index f34dd7dc0de..62a214e2fd2 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java @@ -172,8 +172,8 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers boolean facingVelocity = emitter.isFacingVelocity(); - Vector3f up = new Vector3f(), - left = new Vector3f(); + Vector3f up = new Vector3f(); + Vector3f left = new Vector3f(); if (!facingVelocity) { up.set(camUp); @@ -189,6 +189,7 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers for (int i = 0; i < particles.length; i++) { Particle p = particles[i]; boolean dead = p.life == 0; + if (dead) { positions.put(0).put(0).put(0); positions.put(0).put(0).put(0); @@ -202,11 +203,13 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers camDir.cross(left, up); up.multLocal(p.size); left.multLocal(p.size); + } else if (faceNormal != null) { up.set(faceNormal).crossLocal(Vector3f.UNIT_X); faceNormal.cross(up, left); up.multLocal(p.size); left.multLocal(p.size); + if (p.angle != 0) { TempVars vars = TempVars.get(); vars.vect1.set(faceNormal).normalizeLocal(); @@ -226,6 +229,7 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers up.x = camLeft.x * -sin + camUp.x * cos; up.y = camLeft.y * -sin + camUp.y * cos; up.z = camLeft.z * -sin + camUp.z * cos; + } else { up.set(camUp); left.set(camLeft); @@ -273,10 +277,9 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers positions.clear(); colors.clear(); - if (!uniqueTexCoords) - texcoords.clear(); - else { - texcoords.clear(); + texcoords.clear(); + + if (uniqueTexCoords) { tvb.updateData(texcoords); } From 37788c50b437487b474d2e554fe6467dd13955be Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Wed, 27 Jul 2022 10:23:37 -0700 Subject: [PATCH 011/140] jme3-core: add tests for Transform.toString() --- .../java/com/jme3/math/TestTransform.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/math/TestTransform.java diff --git a/jme3-core/src/test/java/com/jme3/math/TestTransform.java b/jme3-core/src/test/java/com/jme3/math/TestTransform.java new file mode 100644 index 00000000000..c3f80f8822b --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/TestTransform.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 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 java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test the Transform class using JUnit. + * + * @author Stephen Gold + */ +public class TestTransform { + /** + * Test the {@code toString()} method. + */ + @Test + public void testTransformToString() { + // Test data that's never modified: + final Vector3f t = new Vector3f(12f, -1f, 5f); + final Quaternion r = new Quaternion(0f, 0.6f, -0.8f, 0f); + final Vector3f s = new Vector3f(1.7f, 1f, 1.7f); + final Transform test = new Transform(t, r, s); + + // Verify that the method doesn't throw an exception. + String result = test.toString(); + /* + * Verify that the result matches the javadoc + * and can be parsed using a regular expression. + */ + Pattern pattern = Pattern.compile( + "^Transform\\[ (\\S+), (\\S+), (\\S+)\\]\\n" + + "\\[ (\\S+), (\\S+), (\\S+), (\\S+)\\]\\n" + + "\\[ (\\S+) , (\\S+), (\\S+)\\]$" + ); + Matcher matcher = pattern.matcher(result); + boolean valid = matcher.matches(); + Assert.assertTrue(valid); + + String txText = matcher.group(1); + float tx = Float.parseFloat(txText); + Assert.assertEquals(12f, tx, 1e-5f); + + String rzText = matcher.group(6); + float rz = Float.parseFloat(rzText); + Assert.assertEquals(-0.8f, rz, 1e-6f); + + String szText = matcher.group(10); + float sz = Float.parseFloat(szText); + Assert.assertEquals(1.7f, sz, 2e-6f); + } +} From ffdbf6bf97bcab8d80bb01cec64da2e1f0f55a04 Mon Sep 17 00:00:00 2001 From: Lukas-Habring <102620478+Lukas-Habring@users.noreply.github.com> Date: Fri, 29 Jul 2022 08:48:47 +0200 Subject: [PATCH 012/140] Fix issue #1839 (Memory Leak in DefaultLightFilter) Co-authored-by: Lukas Habring --- jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java index 8734ae92aa5..d6f688662f3 100644 --- a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -59,6 +59,7 @@ public void setCamera(Camera camera) { for (Light light : processedLights) { light.frustumCheckNeeded = true; } + processedLights.clear(); } @Override From 5ae543cc6f35cd93b53eb511cb912f91e210749f Mon Sep 17 00:00:00 2001 From: JosiahGoeman <31492985+JosiahGoeman@users.noreply.github.com> Date: Fri, 5 Aug 2022 03:50:04 -0400 Subject: [PATCH 013/140] Added getter & setter for FilterPostProcessor.depthFormat (#1841) * Added setter for FilterPostProcessor.depthFormat * Added getter for FilterPostProcessor.depthFormat --- .../com/jme3/post/FilterPostProcessor.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java index 5422a3411ba..f7b6d185dfa 100644 --- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -86,7 +86,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable { private AppProfiler prof; private Format fbFormat = Format.RGB111110F; - final private Format depthFormat = Format.Depth; + private Format depthFormat = Format.Depth; /** * Create a FilterProcessor @@ -550,10 +550,33 @@ public void setAssetManager(AssetManager assetManager) { this.assetManager = assetManager; } + /** + * Sets the format to be used for the internal frame buffer's color buffer + * + * @param fbFormat the format + */ public void setFrameBufferFormat(Format fbFormat) { this.fbFormat = fbFormat; } + /** + * Sets the format to be used for the internal frame buffer's depth buffer + * + * @param depthFormat the format + */ + public void setFrameBufferDepthFormat(Format depthFormat) { + this.depthFormat = depthFormat; + } + + /** + * Returns the depth format currently used for the internal frame buffer's depth buffer + * + * @return the depth format + */ + public Format getFrameBufferDepthFormat() { + return depthFormat; + } + @Override @SuppressWarnings("unchecked") public void write(JmeExporter ex) throws IOException { From 0f86fed621c19d90ee5c31d51c57d8594c27fc99 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 9 Aug 2022 12:35:05 -0700 Subject: [PATCH 014/140] update the Gradle wrapper to v7.5.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 ++++++ gradlew.bat | 14 ++++++++------ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 21931 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 delta 20228 zcmV)9K*hh*+5^MR1F$OrldI+sv!!iA2nD4BB1t=w?R8Fn-BTM?6#w09HVexJQb;J2 zwt%)Z1WKw_w4|+VfoNzbl~53^I+x@&Y`g5@W&`~j`ruPv`l3%xUwr9|2-TVPO=tQS z`1PVQI-}!*ALGZ2Gmht8!bhio(=nNxd-t4s&hK|V_U6GqAKwG;4Bj#k#|!mn!3ik_ zrO22hPS)Xnl!?=Lu>lF3k(#q6&S9tl!x%A;HSm&&C|-`7nSuJ4$YE59^9IHYTre=s z5OKV6S@;YcdCxDW%RVnTBE97Eg$3cK^U9cEs4EFalzAW+j&65w*jsWPkC!g`UfCCw zO5Uyn!d0$&7ksg3d)3Ou8Q~X&8!)gO;h(f!J2=gMa6Y*UfyaXEnPLbJc_rf7l($`R zp*lY+{7F9Rkfu5B6}dCTeOo@)l;L2`t}t{Ciz~e91Up4$uyQV~Lk_Q01Ua1Ajn|?7 zh(@JJlxns@z=LXKXpXyOQDSIG=CATao_0l$zBG}`jE>5j3|=b901S-}n;D`-&!wP2 zUby9dV2&y~%3!Vsml30cP`ozA7it+NBv*I66}&78UY1jWdU6e`wOI9ivOL-|>AVJS zd+FTx$n~OF2yD+K7Hw47V%4E3dBjb{rFJ(2UcjAonz2oa>ngM0Rmmr7OP0~~IQG5tB7)^aJ|vBTnEa#V$ph32lSjAf7@}u^U7WSwm{qtIqY&UWXQswUCzHzlQ^ob3$K*Nwi~!@1h}u=~P0eD&9uZp#BM>Gwu2c z8t>mx-~&X^s-^J+>PY@fMg4^u^DB=xke(NrbKnJm~`@K z)tKx?dRdheQ#+ZIrjn|IHgL{efYm^G(G5|{Yl5%P%>!;7Qo+B>V*vx?>q zHd-H1(f;0{)yHdSaXhEcLd0BpK948WKQCQA$Ww;qzfem91PTBE2nYZG06_rJx=!pY z0{{TP1^@sw0F$BU9g|$>8Gn^k-BS`#6#rdB7uQ9JP*d|GH3L*o`_eR1G0H+EP}A&X zg&o|&U0RmZf2e2c0iB%bp{6f;=nrb9>D(0=L`RK>d(J)Qch3Etv*%t8{(k%fUHT13R$(*^aXr`KwP2FISW;9JPLTNdhRR}W>(T!9vWys0265KT8Ohz$+)B2{C z*5zdP$poVeO)15UQh)fSZX`>5s;)6~d3}*r@>@BmDQ56=5M^*?c-|v7FT#pR%UUWJ zHw{%w5y(LxQ%~q=hH4AHm{o|sGj7U>*RyiQs#U-=L#Ox5A_hl!2W?ve4DIIt8N|4r zGZIQz<$ZJ>xdNP@ga$N9c!);=9!r?P6A4cd5il!Z4)Y9+<$qO7<zVyx zaM3Kpls7pgOYnv53~yT5-dj2n$I^EnLsIj*FM?yJjK=1dR~ULOnzw`{eU-&ngiNKZ z$U-Qobk9)3$A7#yf}SJ%@gc3^Ez#(U^?OgcPev35f={=pADW0t32HlQDjUVKsoUl@ z)p?=ZlyvwM-~~fnY;Vnm^2KTNZ7r;ReF~iPdQ>W#4lLO8KZ&@dKc@#e-*It zdjy6nvlX7Hm;hL9D;9-6X--}j~&BVY%46YL69u9Lk=-;Ux z;fbbyP)h>@3IG5I2mk;8K>#EG$$sMx003AZ001EXlcDG%f2~>xcpJxcevbsPOK^EX z5+&$_WgQd`(2{kMmTZxtBuKnOkd!G|l2^czgau;Z#X=OFIF9YeiR~zMY$tJ?rf!?2 zZksrfoCuUf+e(kft=%^15%);j^hl4iO;6XolCb{_79c=Ew9~KpgxQ%lZ{BFl_{?3a(OokD-_p*s2p4=thZd+5vbk7D|t zMDx!o{fmcQq<>ZD-^BB6(fqq;-Vx1zc<4*?pC0-zfBJ9H{7*Sl|3IZ5dgwqdD=Vrf0R9D#O-KUw@nbM2YU|p^d9XwHPqQ3 z3ikGZt?M5BtlkpS?%&_pe<~C_h7ku# ziRy`|s;|HIK!0Z_bgJVZWS5GF!Mcv#o}SK*0cbci5bW;k9UM5-9qj4~hB`5`FNDP# ze`}b0{hfRF6=h&@$IQ`Dv5ys9rZw6!YUz=f(K2D_iG*Rbbje9rs$krsj~h%L^o9&8 z88zcfHHmrtXf7t_M(%@T_ifR5)ZW9?UcZ0^^Sw8pvT2CP)nP_pWOY|GZuF$aPaD>N zemZ6d|C?bwHl$loF?NV9dn}3|uUg1tf0$@4XxWdm-S@hUm0>eJ5*)YL?_?gYo=HC8>`XgH~*g|GL@~ z-mmZhg%2tmRQQm>hjYwPrqy!-f3s<>^H&uRLX&Y@KUZLLN{FdL^xE}gG(0ymHWdy0 zd?$$%@PumLagjPeGe9ayGqcu^;(^}6^jb4C3#%=9+HeZfASdJGON&8 znzuqCe3zU+$hw$n0T1C+Ot+1}oF~>6k5=KfrRU-j8`T7aPM8*U<1G*;%YkWeeNhP> zK^rpS5pi@>WCjkv*3M4lXl^r^f#PyAnNQqng;6w~keRZ=hA37*L>7qxLXJj{&;`*v zq0tBFL5&`wltvFzim7b@e-vByE@vPla<@hwqVpPkjGjOQ#%wzgNC@N-n^(9;<6gRo zVipt0*%_w5LVD+)tU^_v!bddj=a9w&JgD&yAJyntdP<{9^peJR@-Xl-TeR&GdW=YZ zX#+d*AuWGO$Ui2U;~L+^Cp5ZDX^q~XH{n-daI*}g#wYm{PRs>tf7keK)-^sYnlNK% z@QB8vJf?6|<9qmw#xY^{=NaZyL0sf2Ar6pm|ba)N154tRQV>5a2ItHD2^C;fQ~ z1H$Zt!uM)yaZ+QO5mr+8ti}_Z(D zPc-jC@%u+~I5X1ff44I-HGV(6DQvnQm7ZTk8h-#2{D5daD7^BZ=shHwhchca1m7-z zf`FA-Bl~eGKw;kGqW#hkzis*xx|KBiLML6P*O|&>{%L%kA7MIwbZ>u8u;+k(Fe!F) zaA2U%FEQ0$2&#VbtYP`}IGmj{!Z?!sv$!dgWX~->7Wogze{}FiP#RYBbV~39{CzP4 zh$@yPqj04^l~WiBphkr{(~92bK)5?&ghnsZRgFK)AJO+>V}n@KyK;ji2O?!+(PV`-)BtUS|e6A)l$Ol!y0!~rv3B>6KM ze?kC(1n9t72d*_|#x+vML(d0mY^ z$)5t86+xQdzT9nZ)j}Y;8TX-EvL)z19!{cScOGoN_;n#4jN*Ch`E}h@5dMKN%bdtu zOP3TqbeRtSzul_EWhOrxCqWmmioyUdmfDl@EML$~Qp)W9=e*E)l7{UZgNRt(wV;4c z%BU1-e{~DQjH_$1hyLsp+C6?I619@@B7Y2JWt-A}InL}|5`|Ge|LX3mFMeYcb5+=G zJU?*D=g2I$D0{K1e&gO0?)$Tj+F0biSNtud7Rw!Z&Ow6Pd3{jYAtmdP9K8x&Daf6r zd2T7ZT8n!m#3HtK_DukO3PQFe-y6#6kGG3qe@#KU$*D@|+IPbug4^(Nk2T?#nH29~VlocV&F|?(?;QMXbNHPL`9l1vojXh#7EY!d ze@sZnl_T(>@R%XMQbGTqnY1&#J^;AW(?ve0=p9KJ;+POszTeVE$K?$>@t%@*J|*~n zTPCb_qk!~Sa!x*E-E=HttVBK$)O^25Vq2y*3ZT(9pUrtyfs;h8IO5j7OCYlfgk!U> zS$7m!b9~;Kd@1u@+?L&F4$g?i&zfftf4^NtoN;{NG|Ii|35T^$+T#0LU9laC&j>5) zI~GbokrlJ=aqbb*8rSVPRu$R&4U@Z#Zlcw6jF?O+Cm-3ALjNogmCyt&r*kx!8{dcV z`|`%`$N2ud@dq$|pkVA3uVd(Y#T%J?KI}a4QiZ1nypPa_(S8J@K`J8`p5+aVf85kO zMSMw$c~ml%plul^|QWD^<}pT1?yFydAWj zc1oMJW+dlq+K{tpgWPV3>^&rHe-b@moeNaFS~}LGfQpir1;atKoT_s-~%O zn5U@f3RMf6N~KLzQcfGy&~92mw@Vwe%zDR$C-H-Z8sX-T(^JruadW9$S>2STnl#lO zZ4i6*&Tcj%xE;>!K!2YU?9VL8ZLXT0re~zGYWf6y5-UF?l`)+{|Jkgvf6}w$mY;N6 zxrbZJ8n4izG%ap*Pt%g&X{sBB;-yoxtjFh0ldsj)(CBkb(Q^2HMXTa-c~|N&)YpvDAg(yOZulm|0c)p6>f1fd2q}0NT-`|6J|t#8HBks@JAgD9L^Ovmdlb|=X&5zsHyx)i-9;mG z0(E~9wR{R`dNF?*f8|{CO66vW0$@K8-aBwBJw9(Pxlh3F!CDiwb>7p)V_RQdrC` zK+X)+FUZA`>*l%{7_SuN1NhBgj|G$DOtC^+BMM!d0k+f>V{ra}1}FVU1tIE zJOs+av=-PVe*#Icv;i-sA5eiXVMgL@x`B^PKZu+ zb_4xpPmMh}5ZjXju{|_}1S!GlopePa^po-gDft0ae;~4pbN<`}rkCkz#-AL6Qa5HU zz-@hLI?~4uRO}YG%wIOVjbzGM~#=hRI{Y zrH$UZ(sTk0$G=7=FJk50Vx?ZV(&yr0+^sGduG0cN5w8*iy^oF{`29G9AHXx?qXu|} zP);h!e_g0KS5dpje+h0P>eFboNIWJQ-=d9l>vl$mISo~}9exU)9em$2d6~sTJ zCTZ_UOuj*HI(B{=N_Pk9-3UWJ9zxM;nCOWmueg4b|J zT$6h{m@&xNn;QqhZ^+1K2*hv7y?Jp={FdCC56AyE&HbuE~%)* z`{ew8{VG0y530yuSj7k)Qt>cG6?{m+WfkjjMa5mX>c@xWhL8C1Q3W59pC6ZtpHT5h ze5wkc#%B~fA~`=RAxZumJ}-wasQ4njq~go?ih{4I*p9CW%<9YL_EsHf@W=4X#!XStb|ln30kc0mU*+yDdiEiXq)f z8T?q7uV*wKYiczU2|d{_jot0=5U4V0CQlPcZdhBqq32x6HWIsYqVfP*$F>neF^B9J z{YhT)q#hb=|>C#RYYaf;EG!wM5B5n>0NM+}GMIquWa$ilB z(tg&6rfrk_i@f*`6mm(ox1Ws~t~m<6&fw_%{l#t&xG7v1kiwaat?Ej0m0nQ9-cTIQ z+N?tPGNy$~*!*#3rPM8#5lO>t+PAlhYl3p-7Z7{SC2jp|&K~lF@)B*A*&5e>Q>ixN z_%<`0>~FU$$Ns53wjMpXQy+42Ucom6R)r^yYKf{_Cbj8CAyj+Jv=uen^qyIAzE((q zOal*yHuFp}ZtDFS_M%6_6OqKyU<)_jy!xmWme;hjvKfn(){0Ki*@DmM>;-}1_@k7+9 zrv@2B4L`%r75qZOFYzl4F+4@X5Kd`0fu}0?wT9o|w*qrK%<7WmI3DNWb{Ec2<(1N* zzbo|M82@hF9&Aaaj0CgBl6=3H!yg3dJ(#z$R;6rCq`#POu0emqp9Hjj{5+yb?#>nC zS;1d4{1t!G@OK&9f8d&if8rX;!=20vYmq=z!IppF-*Vq$3jU+var{@IAR)vQ zMU-j6C(0F3p$SF!nNK%3LG;vkPV7x5?O4LdEfQZ;YC@G-_>NO~O;ia@U~{XUOqzD6 z-=L8RhAyQh-sRr6#+%mX<|CktTZ=1;F_3$Yl@huiCJPcGg1T{>?Y?*o6oZ`SjJz&`R?PB&= zyC`j1Z+0all7ntrPM&3Kpc8jbSfp9SdeXwxi?n@hZAPy9FLbnjC zDQTgTYUhOp7xkEb(qMSs(`t)llXm!qz+MG)tSfo07L-p%fMPgS(DXxIY2zuvt=XPy zUM1I&bBpIyrq~6I9+1Uts*@QMmshhorsl+VrqaZ6Qd+lo9Nm~#=H^VgvGgvyB`ajv zrOP{(W*I|qUEUY06#3VOCly^U%=*b~rB`aksZOnRO{dX+Hp?{YMVslq02YoZpJGU@ zn0>CPm}kRS>Ao(9>mK=DaqmTZ>Xe|4uM%(e_10MVh!n|PC36=|w|GZ36c+Oc3z%&> zMZJhqUOQ*!JF9olGSA3+qvIVJzMkly;auB|Q)xX;2hGUmcl+6fhJ$3_(NE|M-0dFT zKjg8;D{?b`JoZXW+>VuG=E=tF95$*_Hefh)ztE&H3-g%?9Vn$zY1_=czMM>zq~g9) zQeWv8_MNdF;vC)e@VeQU!sU?%+y$K&|*pHhb z|Ca>tA&3ZeLSPqXQ&7cucivp%e0ScwhVwmn^J(yZ^P4wyj=iKb@mKJ-ym1&)E;%gw zI952s5cYG_Tm~G#6Zl(+J{%+$H;a3yR26AgM^F}7Is)HL4&}Q>QPDRHrP&wsW#B&$ z^p#&mWnWpKs;AEv(0VeMnnCqAxki$wN%DbF)N*H_xja}d_tph{jTuaDt{B0LW+kYQ zS}}@$nPi!j!R!ozL9Wbc_6PmTM=)1T<~3I?8^Qc$HK;a@VnJW9aukAN;HE%m7&nh% zVPDWcj9Z4WXcUVHv?PQ2akIB0z_FfQ4%5&ERAVV-VHxIQIo4nWImD3`!ktd+M)^ECOm|iygCqQ!LJ60MbQoon z^B{B_Bi9}z5k)^;ew17Wjx!tvoj!m;D3o;vua1Wq$Me+U1Wpp|0`-d{!EhuUIRYlX z`S!?0IZCW4(lR=76yd(cK*KN^N3fJW%#xPok;WZTO~rt1s6z*q(0pmsOcx3km4Neq zb<{CRl`p=mz_r=5s$%?>x&JN}CD)F;-&}tfXq})o|ZwoZ+mFJI~@AweO@=XYnL{&10&$ zt54=%Eqr?wta}WV3hoMZDHN*8M`zaHJ|_==1&x8K47S~i>2BmX>Byi{sy%`_F6qXy zyioU3tbsYqwQ+YYaB|QUS_UzPV)&xXidml(Q$339M6aQ!VeBZ5&dEHu>MWdKDod`X z{|}RcoA?AdY!u>?f1EFWSb2ODcNPEsvd1iw0n$)H7igPWY;!N+DulyALNRR;AR&YV zq@C;zn<29^>+CFndQfexN4@KndY@QDrPypj(Z>5gYrSvLR;@=p>mT-0MSX8(H`(1R zDKVeV{?2#(-uu4y`%TXM=b?uItinI$VFN5~lH9zI8=IRHH;#;d7NjK{krBd(grhQK zqZrQ96n<_;MNyi7(nUe3*(^Kchl!K1rnyb`Zsl2^-k4ekly zwM_cD5MIx+-XP7v3#nkgZ7I zJ>0xk!uvvae+VCc2;qYvd`LzUKFk{*VQD8Md{o9d+%MA!KPKVh5>86^gh0g+)mULz zQPmjGlQ-#xCa|F6uzEy|=vIX18wJXlCZ?yHHr*Cjl$+W5VA|0wv)4AJm`u%y^mexs z(`8H+wai0$JZ-B?Cs5mA+3`r+R%3=18L`!5QnMp{Uf-I3PfGmZVl_QO>Z-NtdeRAj zN>7=gn(;^v5twme2s%T0YQ;){<)yT=n<+;%45r(po4T__;I5k42n(H1YL+|eB_C?0 z)wO#C{H<1uyuPqQH?^*GVon^u~eHiVj7kjBgO&3T1q{nwH0GcajayAc0@A>k97D7 zPde=zkq)9I!BvH>JC@A3ueykKQ=vPy5byjRM~x1DcdAL3MZ%{foRaVWSvzHVO2TP@ z%X7|jBf4|&uoh+A^Lq5SsXA$!)NP$fkY@m8M>K8Qn(0JZ$%(A4ggtVPmA0dr=cHV0 znwX40v)zmuR*In1sX0SdOv0xXJcuy`+i{bEP1vkp3pdZhjS9A6n}SxfDcFGw$;wxy zU>v)D1eO#-bX!_CVw$aB0%sIFgtHXaCTm#1XL!B?pH=WMCKY^+o6qyw7w|;|U&5Ca zd<9>X@HGWr$2kSxz&9m4qTpM2RKd3~Dd9T`zKib(1e%hn?I`#@en3X$06@B{S>X>Q z{7Au%nd>L`sf3>?_&I)|;5>e*;8%D|!Q=RUwSwQU{@)@_m}%1t&0%)JA9>uekCC7! z@H+{=SMUe?QNfe=lY&3vFGO4dn1rZSD{aK8P0OiHo44!9YD%DL$D&R&352>eHD#GC zB=xU+;J@MT3ZBBGz|v{&b*D{7PiRv@*;jOgo$Tc0vn4BOFUE|(m9v6I;QC8U(Ol4f zv!#p5c40bD(V1RocQh(omYwsGYf+w;mR?*bp*Cu3s^jLaz=o2Qwq%W*QJ;J@TqNhm zHD{N~r}pwdqIs8^(2BEg`Zi$MCY6!Kni6Gq#!?pM#29icZ%N?Vno?!IxPF)GskR)@ zTyv>z1@)9?=R&e`>tM<<(vG%Eb%w})F={lbrRbtsNmo^T&R0<3G3HR2vuc}JZNqG8 z3px2TIo?&wTRN7dd5dG2hwPqXDMzEL+^5-g{spm%PUg`0G&PZD^=j64^s+2U%Y2Bgdv>#nm}fp7Li$wUtE$Q0&lN#`9dXoPh%6rnygx7k1wq^Gv*piR)4P{eeHmOfiuLswRF0yV76dPP8;d4zd1u1}7LOuCUDc`6SVH|38H9;`X`d&w z@;*rZ6Y%>s)7(FSWnIgEM=?CB3CpKUXz_>rSy5sFS7u2ouOfoR46Y`k4641&Ygl~P ze+JL-A?)|0UE7zlcmgY0+}-C2v;@L|Gq_G*6q|W;y`bl1s3lmWq=uA)gLF*KnyjL5 za00b`C;mH`l^n>RE`xg3M?czZ$ZnK*Y8y}Bww6GV=m?4QEM(z-l`FleFFS26P?*QI ziY+3AtEULUft(#a@<%-dgb(M2WT|T{jjJZGhT^fd&z+n)i*@}xx?&tROhSl*A|aCW zG4FRVbvLeY|A$e7)r9Xggr~LWp%43gG#ezm#|hsfg!1Er?>hbrt9D2ng*eA}TUp#>U@f7xp zf8v0+fLmQmAA(H!mti;d5Q0xqPW6&@Kgq7gUK#~S(th-jZ2?Ag7W*~garD!!z=*h) za%(^F9vAoE09owA)1neDz(JC79u2PK0BHjhqWAW0BTuM*w9*Xu0`^G=N$7qC3+CTQ zb~!@A-~}v}5S0*n2CauBH2n(){*4GxK_fOl2|84# z0ssIR1^@s7EtAm57L&m08k0_?4S(H2Eq)-Pd`nv@TMC*G#TZG9CPgnWP4I45hO)5z z$Zo6tBX79SM558eOE3IU#xt`^TS^TyP0!5Po%cEK*_quxe}DV}P{JbvapYxKPEaVw z@Ia<3I*M{!HIP6_#~OoC_4vLkUN&liVYGb2-*d}pST7t`Jf;f=+;Q8U*nbwj&#SZ| z6RdD~y=v{WJf~izReHFJ!F*NsTikWG4uyTJ(z@`rT<-hAXV}bMROiYKuWAJ*tPdV< zHic(}l!aaz)zP*Z`&4AC?9|2Uc5P31Z~309Ts3U&R=DTLJiMsa&P?lm+qNlT*vOvm zaG2`xCr;gIJ!P2hgA8b@LVspkhYnR7rh?)472!Dtj@W02W^?ZtQadefA8+$!*p$Il zCkv~^B10j2Ww>NTJ{G%xk_2oF0q8#(XP`9++8i2m{sbk@+ERUWGG)@(X|z3C$g<*>R4x3x}qZ!6SytILxyzM+nc>3VSl$6CjXC7n^eIJ zy-^8z@gm4b2Q+!Y*Y|8po*oNPhVgTE1|K=$8&ALnXkbp|Kex*epiboI=h7 zGECwQpk@-z)J!%Tp?@De`LN7$8s)uI{wuWK(6vv{q9=4A+T(Sx$7?DC-=lvFk>oR$ z9>FwK4R}__i;?ZvNng+7J)9V3C5Oawn7<$PzyHjLZ_>yfFz~w5=7j zt*u&W(N?Wp=z^`NBxowy+FDzeYHe$+yWJPNecFD0rA`0mzM07+c?kIX_=Wr4yZ794 z&++2nm2ybq=^o;rJQ$=P z&yca1(##6-Y(7((aFFNl+#ns`v!t1)adD8Q@O+_Ppm9lnOM`S5muXxcr0HA{q`SFN zdKSuCmAoy|cyW-z918LhUK*r2UM8Q*rCA}(%JFoJ&kpb^jjLsNb&%f6Yozm>0DrHQ z=ea?C2iF9+Rz~VX`gKBBAEY8)AK>%kxk2NGAg$z$8lNAeRag$4jnZtArb+m0mZ@6; z{7&iFs&TW%+XB2jz&oU4XOL?7UDC7!>3QCz@otUZEw2{@>3n`qkT&v#8ebHo&BA>n z8v$;Wk2YymYTO>A?QCk?5#&zpl7A)=q@B`pagcU%D8PH<8I@*bkYgMVa3aXve91Vr zI4LUG0Zz&DQW2;}1=#_tPKjGLr+zYu;vWYQbrN!y4<>$=RgJ?b-VT6Iw)nKYA3p?`Jt>ua_* zZo6<@L-V$+4Yk|1HEeFWa7)d$4NL`%7aNxvRZ%0}S=DS?k$C57rU`Wk;TN}e7}1m& z;C)Q~Xri;zw3uczCalh?PRnSInpHiP(cNuYRgG#8GXw33o_I82v@^|iBWzfg9+y?R z4ZEubBF0*y!g;RSge|!=n13|g>}`vtl95Zz^^vGq)7EAtlbejVp=7Ia<4}LX31H`6 z6NyLcwM_3Rc?-SXT9cEDUAlwGTbF1znI<(x;$~AS)@oYY3=E0~5^Y9whhatJJKgEE zyCU%1OxKkiUqkv}n`Iidxh|5lnO3=Ku+w?Mp&gOVlx5hFM0|CrqDpKcu4v00 zXDU5qR?w&&%UhAwlzeZuqD&JV_Hom$+P<{`B!#&o&0WTlesJ<>|P~)r6 z-8j0NY1v7wJa5b_tgOk(>mpWGs9~LTwfL?`w|v8vz=_!{(~=rr4Yy#hEfs}%a|E7S zGLlQFTl6r*G?5Yj%?v#y{Od}@I*4hW}8@9nT4(uHXn5K=9s#ZxNj&8P%wmqAS zZiO?AuhICU8hu&gk1akz%i1t?|c))l7%4 z5-TarU0yO)v6Cu}$kt+x)8GW7%}yCn1(k8hM9OM2RX~h4d%Mjx+iX`OfvAH?s2X<1 zQ?BYhK?_JH?SCFgs?j4@q&dGQj~-T_P4U;)p*TLH6`6zF7^STmQ1Pb1ybTG(AEWYEEB*! zW4BwL@Br=_{TeP##rH;_@tLl1mg@nZ8Mm#ztP_-hF|`U=tX@VWt-%A?93jDyVX`@= zUoxYxihl^QigK9M$5Sygo7z1}EN{Ch`-`?WlPZhGuI@mRKcVp_0oArdcVAAXVp>?@ zn!(&q2voHRCCab*OMba#mX34M=%R~zI4L2i& z>nhngDZ^;FFj{l^jB@L!46hX@=XKI-li{^ecz;!%4zFFqlh2mP?>vRcr<-Z>dY2Bb zvPxE2ecDLK4X5#GR*M&%wz`-dY*rcGiHS@FzEH??dW;^|>38&dogSbEb$Xdz(dm2i zuufOdM|AoSeORY{8qnz)z77kYR@Ew#uGi@*x>~0zX`jY7==>?(uk)w*MvXrs9|v^4 ziGOd_`Lld8YRI=h`(k1CIh}9eTcJX(hKTp(4K=R)i8_q2i z!V8L%3&QOQGZ~I2>@X@;+la)&M!XMX7Js(agru{D;rjGm8@3bS4rKDM*^6yC+817& zrR!UWDq~o<&8-)sTj#s^9-WVHzs>`E2h#;76e7KL5=$h)v9~9I&PVxPjqlTWkiW&W z@#Gqd>kLbnW_1s{%mU~8`It_hr`vUYfFIQP+b}72?U1r3(x!5IIMLxYHQZsqxqt0* zisQxc7J%E8CT8@7yNpZCaI0y?!?qFYmLeeB6S2D%7RS};z>003wonuc=N7nO)MwS<;(uguJd;qvQeDcB)1CEYTe?oHR$c*{aE&VV#ti8E z9ljCu%`m>Urs8%aW@hUU3A%?+6%1$J8p|^JBn9jIU3yXH@A1Pre_!4nfdlCUiHTrq zB%Y3AVekV~0Vk@UMxZ-$D)6;+#S$oaJS&$k*ZGHtHE?-U=f@b~`-A{~s(*WB#}sne zyqz(ff5cA;qo<_#@d%}|m7mT}i$%O*Pl>XhWXMKVa611~$Y#HF5vTFbbbf|^uJf~! zJB!BVn6wGX>Jq7FyNVo?xro6`og3~RE_A~k39C9R`R5lJKd1Ba;utNFTo^~ir|}Cq zzsN6X{Ibrk@T)ril7EHa9)G7k)cM!^8=Zg4ze7ptS`q2=Xa2p;e-KChk^hvj@R+hq z=hr0l{aM^RbF>pSkErLS<)-1>A+i5o#2tUt=^yk@o&UyP0t!!@{FxS$Lil8MXS(oou7Tdx zol3zdvDJAftKRShOAvI~>y<0scA+-XYNxE6dj;t?(kHYU*Rz(w1$I|}5eGNBst&>l zxJF#`IOPHq91jJR27mE3Xt>zr`k?eA*E?RItX=T6yS7wrj8fh0hAp)hIvmLP+Mto$EPObh9wIisL(->yE$6DA`{A|s>Sbu8vb6mwb_4A!K8QBZ# zdf>@17R&nCE$nL(2^%3`bZJiCtB`&si`naB{Bd$=wcfdk$_E6; zs1Zr7%ha~8r+?87M4ff=dqXTS8N$>V@kAW8Y1ENsYKhB*iHe4#SX#u*b=2_fkk(^F zY}6gt3{-69Wb&e%1U2#&b(;I-gsgYQ@KE}eOL_wmwT zXPSRXORmmva&~ChSmZ8ndvo?jsGNb-Dw{PXdXUx)$$yzOa%o)G&=7%U@8*sZV7fuw zLM9yyxn9eKN^-q5@LRR~~(k3gpfK?*(!Jp`KUL zKJ~ncuEz5W&|X6yMf)*)T@DUjJm-}S(73We3bquC&!H%ibQWZoN3*FIZ}aI|jDM6lJn0kkNh0+oGO>CSsq)mD$mK!r zb#y&?M4F=%Bn{8C<^42i6Pn3QW%tlTyyRDVL*9NWsP@U@jA}pnCxrZiG^M31J4&HV zgEYORe1K*&c~*GyC)2kA)xJV+-mNsVGUV&0nJc`7-dl$LS`qSj3ZdkzgG0Zn?5EiW zNw4>LtF@5UPiJ{= zqwyi%wzKrt9Hj(HZXp(JKmRFCHdN>LN_&`#>5R_dc}0JM+Z3qZafu2(UHJ^C?DS% zh50zm*QoAInlQqZ{WOq<<`8)LM1TG=c+l5Wc`q$sIzUqjO1$?|X^W?#`6!hgrSdy5 zQi05KD~2jZ4|(pTg?R*s3Yw2n)%QWPXcUnQEWT68AikJSB5oUP+Iyh$eA(j+J)m1)BHDwh8w4~ZwDIvP_CRz-%F56kK zTvG~`H@A4vv7))fSJ~VG)QZB@zCl{q67mhu*$*7f;?L1}KbE_Z#v|yaz|bMONug#b zo@WCywZO==DrEim4$`-wpMMY3E9pCUlk`)UwL={}q*A23Nx0^zx9_U3KCF@`{|gLr zHT)huoOOJjis6lev0a}B{nkHzyv{IaTY=skyg}&Qqjs)ToCkW3uKzc<; zyO-AHkrR9`R*ZJ;*TMe~41Na`{RT|~fENS8s~}n}-Zub*8Rsshe18=N8Yq|1_vsNR z0Lik7ZcIBofSKavysUGbp8L@w3YU{-2uV!K1jaFqetWgH+T~Uhs|qs@cR%gH+q%zn|(_JO%6E^@4i%9IjvAJV56; zd3*!%IC4HHS?ZXBRZ1JqQFyuKN>-U1u?`{|u6)q#hpD5Mo^bz&tH zXzr)xoyd=3;%!X_X{N(=2VSvL9Hn>lQ;T%$5)Xy3S7?K@8yw$Va6v!4M_`CYKV8^Q z7afK+g$S^#XuEpefF$C;a2HKQdmpSl2>acSo%0wd9s~rxO@GyZSuJ3-6EM06SlI(_ zTt+{@{3pTbQ3U8?@X3?l_Beq21on*|VPAL(!2L01{zQ4S8*tr;-RKF7N$?ee{wb{1 zMYBM48P;4%Tj^;~d$Hd6^i%p7d~*$GpP`?lZ$CWy3_YvB{!kVJ52>(5K?@B1LV>*y zsCq67_Ie5ghJUbbAmd~F)oE0#(eoN@)R0igYwr(ft6W5tfb#4KZpVo$n$TSsL^kE)|+6GC%+e^ON6JexJ1(RgVZI( zLBv7w#ecHH?2#QNf|n=*b}=Vr;s>Zml&g@B1k~%NZiK?qgLDbv$Z8oeHbBV%vQaIC zywa5l`3LAyiK(80G{K3ko{;vy!J$vdqP@}?P;a3C^0AjLz|L<$I*V-e48kW;Ozy)j zv@dJGCV-9TFBttBO{V`ru6`ZH`3H>mPdbPGmw(`OAHF!n)~vc$-)Y>}}q*=5a=J&T+;h(To%g=;-uHc;^UOW(@!EUT z32#VNR=r>WZ;MT_Bp~v-7-|(FV@#zG5kUJK^ILKwPAdM$MPB&Lg{X%fF7e%GE04SE z(253pWgqdTD{uQY97Jn8_ju9c)lbTEl|TF~`|Ye>eHa3r9%1p9{U5k8rw4 z%7>IAEzQq%_O3k*x%0+3sWW0FFndLv(0NItE89N*f;HBn`*m+9N1A$B#w77b)DEQq z#=l-jn&q4d^g87q9U?4!*vTW$6@ACy%$br>jZ8xfBrmU06r!r?ZhRvKMOv_pLOadO z_n^1jCqCxi?+O%q-pmdZtVUngG=1>1I z98J}DeJzyEz@S1b3CL?Q!EGE}52NGv=B8~^(oH$4DUK4q+2}V~d?iVLjyq?i{xUO_vQ`Ha|Ls*U!VDyUyAvz^5|9{!h`CfY)#R zBLrrqG-6#!tWf%GDH9o~MqiI*Ufng`_1w*SE77j-R@N*tRoQ#e{1#4SCL={2d#3y| zB~S86pv;o6Rb`v3hqs7riCb}ghq4QW<>C3qE**B#n$n|l)&nuMp9rRh!V;8nwdlmq zuRoT2XJ*FusKbT-Erl>Yv8aO%tl8GJOSkwjXS*U8`LmyZT4i z!tRe%rAcVOk-fA4^+8qHBgV}Z_P}}6hUvT}k2B@#VVMGUXM>dIJ*v2)hM~t;dWS^f zYTl0KqJ87HYi`AaTQ-YP^cnBF+`fqBX)h5$-|Ic!Fwd%d#x@l&6c z$Jd)TI=U_9YST~>k#dCdkjRavH_9cnt9^B=tHP#-TIY$nomT7n zWM21SH~d7R2mS=n0Ht|L^t3}&FQezZq;duZBVIZ)ayBtIWKd$k9CuZ}GsFB<@2nLU zW`@wj`+P{jMNQr{t1bO9Dx}3+QPKRALd7t7p|echE5~h7!?(ig)gZ=vwPC~hE}=Pc zEVpx1(v~R4J*cMoz1;F%lh72RYUc_UjkL2ZoQthTB2%R-K%j;0=9ktBB-svTl3n}` z{j;d0v{!;oC%^WrG~ig(?c6Q}clYVG*^0%xEb4C&p7(tYu(BaY6+eDNDDobG#;S=J zzE^$Z;a^-?w6`dPs)1)i zXKGa%yvl0lV$~kh5jn(Fv|?Qrt#0zPuupP25HMY^?CV-@rX6sKhF^*%h@uo`6fL_t zmjL^ku*iGkj*g!+-u7{jg)Ekn@Z@Z(2p|DxVw3b2BQIz8`F7qs?hPScuN~`lAl#rG zHSdm2kQ!ed?~updv~ruh_`q(W+og|aKsS)Sff@Y9i@l(!-?>d!AxbgiZE{(%s7Frq z|B#7n-zLOOcHp+9v0|ULwK1_6luk%XayjO$p2GE(7Y{FWCZ780 z7nFOeY08*)bh|9E>BZ=%@5%31uD}-k*7D^inva)qHc9m1@%OmfuU?u&l4sS?!)#ltHs>y+k+FktWBH z23`q4ou+ZMI8OIK0RGE+e;uQV2GOxS((C&2w7{m6h@z;VJ;>_l_vSJ6`Zb=o zq@X?689ur;(Hf_f&%$*_QQL!BFA}Rs{9P}NoazTIKX_5~@(Nt3#<7;2j8~#bm3f?d z)#_L!UD)TQmY^V2<1JGu(#MlH)GSA3tru8 z&Be$X4o>&T zFz3PIp>{{q{WoR7$tKC?)@qZYrFao}#%YAQYPpwi0kakb#FE;jHnofu#pH55J#njd zYc;rza}rAzn|*GrqmzI|;p%G=(?aQ=UET7(sc8r@5dYeHXf5y~pQ}h$ia6pbEaTet z#iPCli?~;}qOKRjpmjl5)oG)r6V? zHNE&;K}nn7)JfJl5puyZJ93N2P|kVy9~*~oHlJ62(mQOTXXxYnw<1(JvtqbPI7fs_ z<|nSUxS?(IS?CMOk%P~%ap=Plyu#}4V3b*($QC4I7ZY+g#=3>%CKQJizarNe+>m8h z*Huj0jALg{bye9M-dL*Fkww%5BRMLNfw1XqvS*nvQa@~kIGYPdWuH! zKL`r)AjOw|3fyoioKcN#aqr`OUSJxQ*u1=~fH^o3FdHtFIS;hMi!V`^7_2;4C5siKj4rKx+(MrSy?^zaADG@E+v>8vVHd z%Dg;Oh!!AraDyrw_%Y4~(1oxuf!@>uADzTNEoUPe!T@Z~vmvM@v<^Wa!(cR^`7Z^Y z@zDW{RB&}c5pl2_&MyideT3zo;y4nBgT+05hvL{l$QW8GNM)IqG@v<1Y7duE1pr;J zhv`djXrTnvM9G2!lS^!f1KWXqH3s&}UFsije|L}|unzi~D^g7Y%lwG_Y*S0Hh)*mB z1cE%^cUFlC1%S}+X(7@Fpni2K^kx}_`0w>RNC+H&a<%?xi5@_bWuZ}4RF$QHY5&I{ zsBte11hNk1ohTph6SoB;Ed#{C;OiHGkA~S(LotaJS!k>)*r+^^w1R?WdIHn$Wq?sK z8?e0sho<^~DguyQWdqKQvmpMPWne=>zZwGvY9$zuf{6f2qbR6R2#^Pp1T@A_(2Nkk zcT5%98UoyB;RU3b;J_GYH3q6HHKv4qlR5%JGO{1Wjg;do~nGG}N0Q_?v&=Dda;i/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9382..53a6b238d41 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From c436a40956d30cd71db81698719d277b64065a55 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 9 Aug 2022 12:56:12 -0700 Subject: [PATCH 015/140] main.yml: GitHub Action's ubuntu-18.04 environment is deprecated --- .github/workflows/main.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 191481e66fd..6d309053994 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,7 +58,7 @@ jobs: # Build the natives on android BuildAndroidNatives: name: Build natives for android - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest container: image: jmonkeyengine/buildenv-jme3:android @@ -80,7 +80,7 @@ jobs: name: android-natives path: build/native - # Build the engine, we only deploy from ubuntu-18.04 jdk8 + # Build the engine, we only deploy from ubuntu-latest jdk8 BuildJMonkey: needs: [BuildAndroidNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} @@ -88,12 +88,10 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-18.04,ubuntu-20.04,windows-2019,macOS-latest] + os: [ubuntu-latest,windows-2019,macOS-latest] jdk: [8.x.x,11.x.x] include: - - os: ubuntu-20.04 - osName: linux-next - - os: ubuntu-18.04 + - os: ubuntu-latest osName: linux deploy: true - os: windows-2019 @@ -206,7 +204,7 @@ jobs: DeploySnapshot: needs: [BuildJMonkey] name: "Deploy snapshot" - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest if: github.event_name == 'push' steps: @@ -292,7 +290,7 @@ jobs: DeployRelease: needs: [BuildJMonkey] name: Deploy Release - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest if: github.event_name == 'release' steps: @@ -366,7 +364,7 @@ jobs: DeployJavaDoc: needs: [BuildJMonkey] name: Deploy Javadoc - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest if: github.event_name == 'release' steps: From 13dcf117ae3f7a178cee9849aac47acf426c2310 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 9 Aug 2022 13:47:25 -0700 Subject: [PATCH 016/140] bugfix: mergedJavadoc task is incompatible with Gradle v7 --- build.gradle | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index f2bee119adb..2daf890adce 100644 --- a/build.gradle +++ b/build.gradle @@ -124,6 +124,22 @@ task dist(dependsOn: [':jme3-examples:dist', 'mergedJavadoc']){ description 'Creates a jME3 examples distribution with all jme3 binaries, sources, javadoc and external libraries under ./dist' } +def mergedJavadocSubprojects = [ + ":jme3-android", + ":jme3-core", + ":jme3-desktop", + ":jme3-effects", + ":jme3-ios", + ":jme3-jbullet", + ":jme3-jogg", + ":jme3-lwjgl", + ":jme3-lwjgl3", + ":jme3-networking", + ":jme3-niftygui", + ":jme3-plugins", + ":jme3-terrain", + ":jme3-vr" +] task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the projects.') { title = 'jMonkeyEngine3' destinationDir = mkdir("dist/javadoc") @@ -136,15 +152,8 @@ task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the pro } options.overview = file("javadoc-overview.html") - // Note: The closures below are executed lazily. - source subprojects.collect {project -> - project.sourceSets.main.allJava // main only, exclude tests - } - classpath = files(subprojects.collect {project -> - project.sourceSets*.compileClasspath}) - classpath.from { - subprojects*.configurations*.compile*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() - } + source = mergedJavadocSubprojects.collect { project(it).sourceSets.main.allJava } + classpath = files(mergedJavadocSubprojects.collect { project(it).sourceSets.main.compileClasspath }) } clean.dependsOn('cleanMergedJavadoc') From 6c6aa446476829f798f58643ea1d5745588782ae Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 9 Aug 2022 14:41:23 -0700 Subject: [PATCH 017/140] main.yml: udate "actions/setup-java" to v3 --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d309053994..dc699d47ca3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,7 +89,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest,windows-2019,macOS-latest] - jdk: [8.x.x,11.x.x] + jdk: [8, 11] include: - os: ubuntu-latest osName: linux @@ -98,7 +98,7 @@ jobs: osName: windows - os: macOS-latest osName: mac - - jdk: 11.x.x + - jdk: 11 deploy: false steps: @@ -108,10 +108,10 @@ jobs: fetch-depth: 1 - name: Setup the java environment - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: ${{ matrix.jdk }} - architecture: x64 - name: Download natives for android uses: actions/download-artifact@master From 299852838d8dbc1cd162215637f7637cbec154ff Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 9 Aug 2022 15:07:37 -0700 Subject: [PATCH 018/140] main.yml: add JDK 17 tests --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc699d47ca3..5ca2daaf4ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,7 +89,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest,windows-2019,macOS-latest] - jdk: [8, 11] + jdk: [8, 11, 17] include: - os: ubuntu-latest osName: linux @@ -100,6 +100,8 @@ jobs: osName: mac - jdk: 11 deploy: false + - jdk: 17 + deploy: false steps: - name: Clone the repo From eaec2416248a0c89a62860b1dab393bf2d447ad5 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Wed, 10 Aug 2022 08:49:08 -0700 Subject: [PATCH 019/140] build.gradle: update gradleVersion in case user runs the "wrapper" task --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2daf890adce..842fb81aae2 100644 --- a/build.gradle +++ b/build.gradle @@ -283,7 +283,7 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { //} wrapper { - gradleVersion = '6.9.2' + gradleVersion = '7.5.1' } From f0b7a96e64017dc7d386d1b7f6e86698e0d4780f Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Mon, 15 Aug 2022 07:22:31 +0430 Subject: [PATCH 020/140] Fix #1843 (java.util.zip.ZipException in HttpZipLocator) (#1842) * HttpZipLocator:fix invalid code lengths set & invalid distance too far back ZipExceptions. * Get file name length and extra field length from local file header to calculate file data offset. * Make fields `byteBuf`, `charBuf`, `utf8Decoder` non-static because they are not thread-safe. * Surround streams with try-with-resources block. --- .../jme3/asset/plugins/HttpZipLocator.java | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java index da62023af6b..73a2700be30 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java @@ -42,7 +42,7 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.util.HashMap; @@ -80,14 +80,9 @@ public class HttpZipLocator implements AssetLocator { private int tableLength; private HashMap entries; - private static final ByteBuffer byteBuf = ByteBuffer.allocate(250); - private static final CharBuffer charBuf = CharBuffer.allocate(250); - private static final CharsetDecoder utf8Decoder; - - static { - Charset utf8 = Charset.forName("UTF-8"); - utf8Decoder = utf8.newDecoder(); - } + private final ByteBuffer byteBuf = ByteBuffer.allocate(250); + private final CharBuffer charBuf = CharBuffer.allocate(250); + private final CharsetDecoder utf8Decoder = StandardCharsets.UTF_8.newDecoder(); private static class ZipEntry2 { String name; @@ -96,6 +91,10 @@ private static class ZipEntry2 { int compSize; long crc; boolean deflate; + // These fields will be fetched later from local file header, + // once asset requested. + Integer nameLength; + Integer extraLength; @Override public String toString(){ @@ -125,7 +124,7 @@ private static long getu32(byte[] b, int off) throws IOException{ (((long)(b[off]&0xff)) << 24); } - private static String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException { + private String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException { StringBuilder sb = new StringBuilder(); int read = 0; @@ -143,7 +142,7 @@ private static String getUTF8String(byte[] b, int off, int len) throws Character byteBuf.flip(); // decode data in byteBuf - CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput); + CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput); // If the result is not an underflow, it's an error // that cannot be handled. @@ -234,10 +233,6 @@ private int readTableEntry(byte[] table, int offset) throws IOException{ entry.compSize = get32(table, offset + ZipEntry.CENSIZ); entry.offset = get32(table, offset + ZipEntry.CENOFF); - // We want the offset in the file data: - // move the offset forward to skip the LOC header. - entry.offset += ZipEntry.LOCHDR + nameLen + extraLen; - entries.put(entry.name, entry); return newOffset; @@ -256,16 +251,15 @@ private void fillByteArray(byte[] array, InputStream source) throws IOException{ } private void readCentralDirectory() throws IOException{ - InputStream in = readData(tableOffset, tableLength); byte[] header = new byte[tableLength]; - - // Fix for "PK12 bug in town.zip": sometimes - // not entire byte array will be read with InputStream.read() - // (especially for big headers) - fillByteArray(header, in); + try (InputStream in = readData(tableOffset, tableLength)) { + // Fix for "PK12 bug in town.zip": sometimes + // not entire byte array will be read with InputStream.read() + // (especially for big headers) + fillByteArray(header, in); // in.read(header); - in.close(); + } entries = new HashMap(numEntries); int offset = 0; @@ -292,10 +286,10 @@ private void readEndHeader() throws IOException{ // In that case, we have to search for it. // Increase search space to 200 bytes - InputStream in = readData(Integer.MAX_VALUE, 200); byte[] header = new byte[200]; - fillByteArray(header, in); - in.close(); + try (InputStream in = readData(Integer.MAX_VALUE, 200)) { + fillByteArray(header, in); + } int offset = -1; for (int i = 200 - 22; i >= 0; i--){ @@ -323,9 +317,23 @@ public void load(URL url) throws IOException { readCentralDirectory(); } - private InputStream openStream(ZipEntry2 entry) throws IOException{ - InputStream in = readData(entry.offset, entry.compSize); - if (entry.deflate){ + private InputStream openStream(ZipEntry2 entry) throws IOException { + if (entry.nameLength == null && entry.extraLength == null) { + // Need to fetch local file header to obtain file name length + // and extra field length. + try (InputStream in = readData(entry.offset, ZipEntry.LOCHDR)) { + byte[] localHeader = new byte[ZipEntry.LOCHDR]; + in.read(localHeader); + entry.nameLength = get16(localHeader, ZipEntry.LOCNAM); + entry.extraLength = get16(localHeader, ZipEntry.LOCEXT); + } + } + + // We want the offset in the file data: + // move the offset forward to skip the LOC header. + int fileDataOffset = entry.offset + ZipEntry.LOCHDR + entry.nameLength + entry.extraLength; + InputStream in = readData(fileDataOffset, entry.compSize); + if (entry.deflate) { return new InflaterInputStream(in, new Inflater(true)); } return in; From 42c4466987ae0bbd4da83d084531c34528b955ae Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 17 Aug 2022 16:54:57 +0300 Subject: [PATCH 021/140] Clean up the display modes parsing --- .../java/com/jme3/app/SettingsDialog.java | 77 +++++++++---------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java index 1198ecd4f47..cc6e00955f2 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java @@ -42,8 +42,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.List; import java.util.ResourceBundle; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; @@ -743,10 +745,10 @@ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthL heightLimit -= insets.top + insets.bottom; widthLimit -= insets.left + insets.right; - ArrayList resolutions = new ArrayList<>(modes.length); - for (int i = 0; i < modes.length; i++) { - int height = modes[i].getHeight(); - int width = modes[i].getWidth(); + Set resolutions = new LinkedHashSet<>(modes.length); + for (DisplayMode mode : modes) { + int height = mode.getHeight(); + int width = mode.getWidth(); if (width >= minWidth && height >= minHeight) { if (height >= heightLimit) { height = heightLimit; @@ -762,9 +764,7 @@ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthL } } - String[] res = new String[resolutions.size()]; - resolutions.toArray(res); - return res; + return resolutions.toArray(new String[0]); } /** @@ -776,12 +776,12 @@ private String[] getWindowedResolutions(DisplayMode[] modes) { int maxHeight = 0; int maxWidth = 0; - for (int i = 0; i < modes.length; i++) { - if (maxHeight < modes[i].getHeight()) { - maxHeight = modes[i].getHeight(); + for (DisplayMode mode : modes) { + if (maxHeight < mode.getHeight()) { + maxHeight = mode.getHeight(); } - if (maxWidth < modes[i].getWidth()) { - maxWidth = modes[i].getWidth(); + if (maxWidth < mode.getWidth()) { + maxWidth = mode.getWidth(); } } @@ -792,31 +792,34 @@ private String[] getWindowedResolutions(DisplayMode[] modes) { * Returns every possible bit depth for the given resolution. */ private static String[] getDepths(String resolution, DisplayMode[] modes) { - ArrayList depths = new ArrayList<>(4); - for (int i = 0; i < modes.length; i++) { + List depths = new ArrayList<>(4); + for (DisplayMode mode : modes) { + int bitDepth = mode.getBitDepth(); + if (bitDepth == DisplayMode.BIT_DEPTH_MULTI) { + continue; + } // Filter out all bit depths lower than 16 - Java incorrectly // reports // them as valid depths though the monitor does not support them - if (modes[i].getBitDepth() < 16 && modes[i].getBitDepth() > 0) { + if (bitDepth < 16 && bitDepth > 0) { continue; } - - String res = modes[i].getWidth() + " x " + modes[i].getHeight(); - String depth = modes[i].getBitDepth() + " bpp"; - if (res.equals(resolution) && !depths.contains(depth)) { + String res = mode.getWidth() + " x " + mode.getHeight(); + if (!res.equals(resolution)) { + continue; + } + String depth = bitDepth + " bpp"; + if (!depths.contains(depth)) { depths.add(depth); } } - if (depths.size() == 1 && depths.contains("-1 bpp")) { + if (depths.isEmpty()) { // add some default depths, possible system is multi-depth supporting - depths.clear(); depths.add("24 bpp"); } - String[] res = new String[depths.size()]; - depths.toArray(res); - return res; + return depths.toArray(new String[0]); } /** @@ -824,24 +827,21 @@ private static String[] getDepths(String resolution, DisplayMode[] modes) { */ private static String[] getFrequencies(String resolution, DisplayMode[] modes) { - ArrayList freqs = new ArrayList<>(4); - for (int i = 0; i < modes.length; i++) { - String res = modes[i].getWidth() + " x " + modes[i].getHeight(); + List freqs = new ArrayList<>(4); + for (DisplayMode mode : modes) { + String res = mode.getWidth() + " x " + mode.getHeight(); String freq; - if (modes[i].getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { + if (mode.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { freq = "???"; } else { - freq = modes[i].getRefreshRate() + " Hz"; + freq = mode.getRefreshRate() + " Hz"; } - if (res.equals(resolution) && !freqs.contains(freq)) { freqs.add(freq); } } - String[] res = new String[freqs.size()]; - freqs.toArray(res); - return res; + return freqs.toArray(new String[0]); } /** @@ -854,13 +854,12 @@ private static String[] getFrequencies(String resolution, private static String getBestFrequency(String resolution, DisplayMode[] modes) { int closest = Integer.MAX_VALUE; int desired = 60; - for (int i = 0; i < modes.length; i++) { - String res = modes[i].getWidth() + " x " + modes[i].getHeight(); - int freq = modes[i].getRefreshRate(); + for (DisplayMode mode : modes) { + String res = mode.getWidth() + " x " + mode.getHeight(); + int freq = mode.getRefreshRate(); if (freq != DisplayMode.REFRESH_RATE_UNKNOWN && res.equals(resolution)) { - if (Math.abs(freq - desired) < - Math.abs(closest - desired)) { - closest = modes[i].getRefreshRate(); + if (Math.abs(freq - desired) < Math.abs(closest - desired)) { + closest = mode.getRefreshRate(); } } } From f6ccb8e5d331aac2e62456447bbc71fffb5a00e3 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 17 Aug 2022 17:21:01 +0300 Subject: [PATCH 022/140] No need to separately test for the contains --- jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java index cc6e00955f2..9e20251dacd 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java @@ -758,9 +758,7 @@ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthL } String res = width + " x " + height; - if (!resolutions.contains(res)) { - resolutions.add(res); - } + resolutions.add(res); } } From cf9aa9cf018e483837c235077740bb1934eec183 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Mon, 22 Aug 2022 10:23:24 -0700 Subject: [PATCH 023/140] README.md: SDK v3.4 has been released --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4864152c2ea..8be3d3f5786 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ jMonkeyEngine jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. v3.5.2 is the latest stable version of the Engine. -v3.3.2 is the latest stable version of the jMonkeyEngine SDK. +v3.4 is the latest stable version of the jMonkeyEngine SDK. The engine is used by several commercial game studios and computer-science courses. Here's a taste: From 7911a61f47d307e73a7fe72fce02aad4a1eaad12 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 24 Aug 2022 10:37:25 +0430 Subject: [PATCH 024/140] Some enhancement to new animation system (#1845) * Some enhancement to new animation system, including: * Option to enable/disable animation mask propagation to child actions. * Option to control max transition weight. For example useful for controlling smooth animation transition when an animation is removed from an upper layer. * Added animation loop support in AnimLayer. * AnimLayer can now also keep action name, so one can easily lookup currently playing action name in an specific layer. * Minor Javadoc fix. * AnimLayer: clear `currentActionName` inside `cloneFields` method. --- .../main/java/com/jme3/anim/AnimComposer.java | 20 ++++-- .../main/java/com/jme3/anim/AnimLayer.java | 70 ++++++++++++++++++- .../jme3/anim/tween/action/BaseAction.java | 60 ++++++++++++++++ .../anim/tween/action/BlendableAction.java | 51 +++++++++++++- .../model/anim/TestAnimMigration.java | 35 ++++++++-- 5 files changed, 225 insertions(+), 11 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 4bc6c383a14..d06b0816cce 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-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -115,7 +115,7 @@ public void removeAnimClip(AnimClip anim) { } /** - * Run an action on the default layer. + * Run an action on the default layer. By default action will loop. * * @param name The name of the action to run. * @return The action corresponding to the given name. @@ -125,16 +125,28 @@ public Action setCurrentAction(String name) { } /** - * Run an action on specified layer. + * Run an action on specified layer. By default action will loop. * * @param actionName The name of the action to run. * @param layerName The layer on which action should run. * @return The action corresponding to the given name. */ public Action setCurrentAction(String actionName, String layerName) { + return setCurrentAction(actionName, layerName, true); + } + + /** + * Run an action on specified layer. + * + * @param actionName The name of the action to run. + * @param layerName The layer on which action should run. + * @param loop True if the action must loop. + * @return The action corresponding to the given name. + */ + public Action setCurrentAction(String actionName, String layerName, boolean loop) { AnimLayer l = getLayer(layerName); Action currentAction = action(actionName); - l.setCurrentAction(currentAction); + l.setCurrentAction(actionName, currentAction, loop); return currentAction; } 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 9b9990e91d7..0c3c97e9988 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,6 +53,10 @@ public class AnimLayer implements JmeCloneable { * The Action currently running on this layer, or null if none. */ private Action currentAction; + /** + * 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. @@ -77,6 +81,8 @@ public class AnimLayer implements JmeCloneable { */ final private String name; + private boolean loop = true; + /** * Instantiates a layer without a manager or a current Action, owned by the * specified composer. @@ -105,6 +111,15 @@ public Action getCurrentAction() { return currentAction; } + /** + * Returns the name of the Action that's currently running. + * + * @return the pre-existing instance, or null if none + */ + public String getCurrentActionName() { + return currentActionName; + } + /** * Returns the current manager. * @@ -145,14 +160,42 @@ public double getTime() { /** * Runs the specified Action, starting from time = 0. This cancels any - * Action previously running on this layer. + * Action previously running on this layer. By default Action will loop. * * @param actionToRun the Action to run (alias created) or null for no * action */ public void setCurrentAction(Action actionToRun) { + this.setCurrentAction(null, actionToRun); + } + + /** + * Runs the specified Action, starting from time = 0. This cancels any + * Action previously running on this layer. By default Action will loop. + * + * @param actionName the Action name or null for no action name + * @param actionToRun the Action to run (alias created) or null for no + * action + */ + public void setCurrentAction(String actionName, Action actionToRun) { + this.setCurrentAction(actionName, actionToRun, true); + } + + /** + * Runs the specified Action, starting from time = 0. This cancels any + * Action previously running on this layer. + * + * @param actionName the Action name or null for no action name + * @param actionToRun the Action to run (alias created) or null for no + * action + * @param loop true if Action must loop. If it is false, Action will be + * removed after finished running + */ + public void setCurrentAction(String actionName, Action actionToRun, boolean loop) { this.time = 0.0; this.currentAction = actionToRun; + this.currentActionName = actionName; + this.loop = loop; } /** @@ -181,6 +224,24 @@ public void setTime(double animationTime) { } } + /** + * @return True if the Action will keep looping after it is done playing, + * otherwise, returns false + */ + public boolean isLooping() { + return loop; + } + + /** + * Sets the looping mode for this layer. The default is true. + * + * @param loop True if the action should keep looping after it is done + * playing + */ + public void setLooping(boolean loop) { + this.loop = loop; + } + /** * Updates the animation time and the current Action during a * controlUpdate(). @@ -211,6 +272,10 @@ void update(float appDeltaTimeInSeconds) { if (!running) { // went past the end of the current Action time = 0.0; + if (!loop) { + // Clear the current action + setCurrentAction(null); + } } } @@ -229,6 +294,7 @@ void update(float appDeltaTimeInSeconds) { public void cloneFields(Cloner cloner, Object original) { composer = cloner.clone(composer); currentAction = null; + currentActionName = null; } @Override diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java index 31f191678be..99c559fca6a 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java @@ -1,5 +1,37 @@ +/* + * Copyright (c) 2009-2022 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.tween.action; +import com.jme3.anim.AnimationMask; import com.jme3.anim.tween.ContainsTweens; import com.jme3.anim.tween.Tween; import com.jme3.util.SafeArrayList; @@ -9,6 +41,7 @@ public class BaseAction extends Action { final private Tween tween; + private boolean maskPropagationEnabled = true; public BaseAction(Tween tween) { this.tween = tween; @@ -30,6 +63,33 @@ private void gatherActions(Tween tween, List subActions) { } } + /** + * @return true if mask propagation to child actions is enabled else returns false + */ + public boolean isMaskPropagationEnabled() { + return maskPropagationEnabled; + } + + /** + * + * @param maskPropagationEnabled If true, then mask set by AnimLayer will be + * forwarded to all child actions (Default=true) + */ + public void setMaskPropagationEnabled(boolean maskPropagationEnabled) { + this.maskPropagationEnabled = maskPropagationEnabled; + } + + @Override + public void setMask(AnimationMask mask) { + super.setMask(mask); + + if (maskPropagationEnabled) { + for (Action action : actions) { + action.setMask(mask); + } + } + } + @Override public boolean interpolate(double t) { return tween.interpolate(t); diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java index 4ebf3ede2bb..a32495d672e 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2022 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.tween.action; import com.jme3.anim.tween.AbstractTween; @@ -11,6 +42,7 @@ public abstract class BlendableAction extends Action { protected BlendableAction collectTransformDelegate; private float transitionWeight = 1.0f; + private double maxTransitionWeight = 1.0; private double transitionLength = 0.4f; private float weight = 1f; private TransitionTween transition = new TransitionTween(transitionLength); @@ -81,6 +113,23 @@ protected float getTransitionWeight() { return transitionWeight; } + /** + * @param maxTransitionWeight The max transition weight. Must be >= 0 and <=1 (default=1) + */ + public void setMaxTransitionWeight(double maxTransitionWeight) { + assert maxTransitionWeight >= 0 && maxTransitionWeight <= 1; + + this.maxTransitionWeight = maxTransitionWeight; + } + + /** + * + * @return The max transition weight (default=1) + */ + public double getMaxTransitionWeight() { + return maxTransitionWeight; + } + /** * Create a shallow clone for the JME cloner. * @@ -121,7 +170,7 @@ public TransitionTween(double length) { @Override protected void doInterpolate(double t) { - transitionWeight = (float) t; + transitionWeight = (float) Math.min(t, maxTransitionWeight); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java index d98583244b7..f999bece1a4 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 jMonkeyEngine + * Copyright (c) 2017-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,8 +32,7 @@ package jme3test.model.anim; import com.jme3.anim.*; -import com.jme3.anim.tween.action.BlendAction; -import com.jme3.anim.tween.action.LinearBlendSpace; +import com.jme3.anim.tween.action.*; import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.app.ChaseCameraAppState; import com.jme3.app.SimpleApplication; @@ -157,9 +156,10 @@ public void onAction(String name, boolean isPressed, float tpf) { @Override public void onAction(String name, boolean isPressed, float tpf) { if (isPressed) { - composer.setCurrentAction("Wave", "LeftArm"); + ((BlendableAction)composer.setCurrentAction("Wave", "LeftArm", false)).setMaxTransitionWeight(0.9); } } + }, "mask"); inputManager.addMapping("blendUp", new KeyTrigger(KeyInput.KEY_UP)); @@ -184,6 +184,33 @@ public void onAnalog(String name, float value, float tpf) { //System.err.println(blendValue); } }, "blendUp", "blendDown"); + + inputManager.addMapping("maxTransitionWeightInc", new KeyTrigger(KeyInput.KEY_ADD)); + inputManager.addMapping("maxTransitionWeightDec", new KeyTrigger(KeyInput.KEY_SUBTRACT)); + + inputManager.addListener(new AnalogListener() { + + @Override + public void onAnalog(String name, float value, float tpf) { + if (name.equals("maxTransitionWeightInc")) { + Action action = composer.getCurrentAction(); + if (action instanceof BlendableAction) { + BlendableAction ba = (BlendableAction) action; + ba.setMaxTransitionWeight(Math.min(ba.getMaxTransitionWeight() + 0.01, 1.0)); + System.out.println("MaxTransitionWeight=" + ba.getMaxTransitionWeight()); + } + } + if (name.equals("maxTransitionWeightDec")) { + Action action = composer.getCurrentAction(); + if (action instanceof BlendableAction) { + BlendableAction ba = (BlendableAction) action; + ba.setMaxTransitionWeight(Math.max(ba.getMaxTransitionWeight() - 0.01, 0.0)); + System.out.println("MaxTransitionWeight=" + ba.getMaxTransitionWeight()); + } + } + //System.err.println(blendValue); + } + }, "maxTransitionWeightInc", "maxTransitionWeightDec"); } private void setupModel(Spatial model) { From 1a071eb52a0c263f465ef3f3ed00d0bbdb38606f Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 24 Aug 2022 10:39:40 +0430 Subject: [PATCH 025/140] Added a Loop tween to Tweens factory class (#1846) * Added a Loop tween to Tweens factory class. Supports looping by count or duration. * Redesigned the Loop tween to work similar to Sequence tween. Now fast forwarding the loop will also try to catch up the loops left behind making sure they always see their 'length'. * Added the missing Override annotation. --- .../main/java/com/jme3/anim/tween/Tweens.java | 119 +++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java index d4200d71b03..eba1a05f9ee 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 jMonkeyEngine + * Copyright (c) 2015-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -179,6 +179,40 @@ public static Tween callTweenMethod(double length, Object target, String method, return new CallTweenMethod(length, target, method, args); } + /** + * Creates a tween that loops the specified delegate tween or tweens + * to the desired count. If more than one tween is specified then they + * are wrapped in a sequence using the sequence() method. + * + * @param count the desired loop count + * @param delegates the desired sequence of tweens + * @return a new instance + */ + public static Tween loopCount(int count, Tween... delegates) { + if (delegates.length == 1) { + return new Loop(delegates[0], count); + } + + return new Loop(sequence(delegates), count); + } + + /** + * Creates a tween that loops the specified delegate tween or tweens + * to the desired duration. If more than one tween is specified then they + * are wrapped in a sequence using the sequence() method. + * + * @param duration the desired duration + * @param delegates the desired sequence of tweens + * @return a new instance + */ + public static Tween loopDuration(double duration, Tween... delegates) { + if (delegates.length == 1) { + return new Loop(delegates[0], duration); + } + + return new Loop(sequence(delegates), duration); + } + private static interface CurveFunction { public double curve(double input); } @@ -644,4 +678,87 @@ public String toString() { return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]"; } } + + private static class Loop implements Tween, ContainsTweens { + + private final Tween[] delegate = new Tween[1]; + private final double length; + private final int loopCount; + private double baseTime; + private int current = 0; + + public Loop (Tween delegate, double duration) { + if (delegate.getLength() <= 0) { + throw new IllegalArgumentException("Delegate length must be greater than 0"); + } + if (duration <= 0) { + throw new IllegalArgumentException("Duration must be greater than 0"); + } + + this.delegate[0] = delegate; + this.length = duration; + this.loopCount = (int) Math.ceil(duration / delegate.getLength()); + } + + public Loop (Tween delegate, int count) { + if (count <= 0) { + throw new IllegalArgumentException("Loop count must be greater than 0"); + } + + this.delegate[0] = delegate; + this.length = count * delegate.getLength(); + this.loopCount = count; + } + + @Override + public double getLength() { + return length; + } + + @Override + public Tween[] getTweens() { + return delegate; + } + + @Override + public boolean interpolate(double t) { + + // Sanity check the inputs + if (t < 0) { + return true; + } + + if (t < baseTime) { + // We've rolled back before the current loop step + // which means we need to reset and start forward + // again. We have no idea how to 'roll back' and + // this is the only way to maintain consistency. + // The only 'normal' case where this happens is when looping + // in which case a full rollback is appropriate. + current = 0; + baseTime = 0; + } + + if (current >= loopCount) { + return false; + } + + // Skip any that are done + while (!delegate[0].interpolate(t - baseTime)) { + // Time to go to the next loop + baseTime += delegate[0].getLength(); + current++; + if (current >= loopCount) { + return false; + } + } + + return t < length; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + length + "]"; + } + } } From 9975e7276455ceb7038af689aaf4b4081f305e63 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sat, 27 Aug 2022 12:06:28 +0430 Subject: [PATCH 026/140] Added Tweens.cycle() and Tweens.invert() methods (#1849) Cycle is used for running delegate tween back and force and Invert is used to run delegate tween backward. --- .../main/java/com/jme3/anim/tween/Tweens.java | 47 +++++++++++++++++++ .../model/anim/TestAnimMigration.java | 4 ++ 2 files changed, 51 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java index eba1a05f9ee..b6ca9c8873c 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java @@ -213,6 +213,28 @@ public static Tween loopDuration(double duration, Tween... delegates) { return new Loop(sequence(delegates), duration); } + /** + * Creates a tween that inverts the specified delegate tween. + * + * @param delegate the desired tween + * @return a new instance + */ + public static Tween invert(Tween delegate) { + return new Invert(delegate); + } + + /** + * Creates a tween that will cycle back and forth the specified delegate tween. + * When reaching the end, the tween will play backwards from the end until it + * reaches the start. + * + * @param delegate the desired tween + * @return a new instance + */ + public static Tween cycle(Tween delegate) { + return sequence(delegate, invert(delegate)); + } + private static interface CurveFunction { public double curve(double input); } @@ -761,4 +783,29 @@ public String toString() { return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + length + "]"; } } + + private static class Invert extends AbstractTween implements ContainsTweens { + + private final Tween[] delegate = new Tween[1]; + + public Invert( Tween delegate ) { + super(delegate.getLength()); + this.delegate[0] = delegate; + } + + @Override + protected void doInterpolate(double t) { + delegate[0].interpolate((1.0 - t) * getLength()); + } + + @Override + public Tween[] getTweens() { + return delegate; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + getLength() + "]"; + } + } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java index f999bece1a4..6eb0bad5868 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java @@ -32,6 +32,7 @@ package jme3test.model.anim; import com.jme3.anim.*; +import com.jme3.anim.tween.Tweens; import com.jme3.anim.tween.action.*; import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.app.ChaseCameraAppState; @@ -244,8 +245,11 @@ private void setupModel(Spatial model) { composer.action("Walk").setSpeed(-1); + composer.addAction("WalkCycle", new BaseAction(Tweens.cycle(composer.makeAction("Walk")))); + composer.makeLayer("LeftArm", ArmatureMask.createMask(sc.getArmature(), "shoulder.L")); + anims.addFirst("WalkCycle"); anims.addFirst("Blend"); anims.addFirst("Sequence2"); anims.addFirst("Sequence1"); From 7fa8f7d3fc2feb5553b7c8c5685c584776e86332 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 28 Aug 2022 09:02:18 +0430 Subject: [PATCH 027/140] BlendAction: resolve slow-motion side effect caused by stretching actions (#1848) * BlendAction: resolve slow motion side effect caused by stretching any action that doesn't have the same length. It generates speed factor for each child animation that are dynamically interpolated and applied to base speed based on the blend weight taken from blend space. * Add missing javadoc. * Add Copyright. * Renamed calculateSpeedFactors() to applyDefaultSpeedFactors() and made it non static. --- .../jme3/anim/tween/action/BlendAction.java | 90 +++++++++++++++++++ .../model/anim/TestAnimMigration.java | 4 +- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java index 51b7ae3f855..401a9d2cce4 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java @@ -1,8 +1,41 @@ +/* + * Copyright (c) 2009-2022 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.tween.action; import com.jme3.anim.util.HasLocalTransform; +import com.jme3.math.FastMath; import com.jme3.math.Transform; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -14,6 +47,7 @@ public class BlendAction extends BlendableAction { final private BlendSpace blendSpace; private float blendWeight; final private double[] timeFactor; + private double[] speedFactors; final private Map targetMap = new HashMap<>(); public BlendAction(BlendSpace blendSpace, BlendableAction... actions) { @@ -47,6 +81,10 @@ public BlendAction(BlendSpace blendSpace, BlendableAction... actions) { } } } + + // Calculate default factors that dynamically adjust speed to resolve + // slow motion effect on stretched actions. + applyDefaultSpeedFactors(); } @Override @@ -85,6 +123,58 @@ public BlendSpace getBlendSpace() { return blendSpace; } + @Override + public double getSpeed() { + if (speedFactors != null) { + return super.getSpeed() * FastMath.interpolateLinear(blendWeight, + (float) speedFactors[firstActiveIndex], + (float) speedFactors[secondActiveIndex]); + } + + return super.getSpeed(); + } + + /** + * @return The speed factor or null if there is none + */ + public double[] getSpeedFactors() { + return speedFactors; + } + + /** + * Used to resolve the slow motion side effect caused by stretching actions that + * doesn't have the same length. + * + * @param speedFactors The speed factors for each child action. BlendAction will + * interpolate factor for current frame based on blend weight + * and will multiply it to speed. + */ + public void setSpeedFactors(double... speedFactors) { + if (speedFactors.length != actions.length) { + throw new IllegalArgumentException("Array length must be " + actions.length); + } + + this.speedFactors = speedFactors; + } + + public void clearSpeedFactors() { + this.speedFactors = null; + } + + /** + * BlendAction will stretch it's child actions if they don't have the same length. + * This might cause stretched animations to run slowly. This method generates factors + * based on how much actions are stretched. Multiplying this factor to base speed will + * resolve the slow-motion side effect caused by stretching. BlendAction will use the + * blend weight taken from BlendSpace to interpolate the speed factor for current frame. + */ + public void applyDefaultSpeedFactors() { + double[] factors = Arrays.stream(getActions()) + .mapToDouble(action -> getLength() / action.getLength()) + .toArray(); + setSpeedFactors(factors); + } + protected void setFirstActiveIndex(int index) { this.firstActiveIndex = index; } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java index 6eb0bad5868..18b09edbf2b 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java @@ -174,13 +174,13 @@ public void onAnalog(String name, float value, float tpf) { blendValue += value; blendValue = FastMath.clamp(blendValue, 1, 4); action.getBlendSpace().setValue(blendValue); - action.setSpeed(blendValue); + //action.setSpeed(blendValue); } if (name.equals("blendDown")) { blendValue -= value; blendValue = FastMath.clamp(blendValue, 1, 4); action.getBlendSpace().setValue(blendValue); - action.setSpeed(blendValue); + //action.setSpeed(blendValue); } //System.err.println(blendValue); } From 49a3102f52ba17009d7db931066307dcf56f0af2 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Tue, 20 Sep 2022 09:54:14 +0430 Subject: [PATCH 028/140] Fix issue #1850 (JmeSystem.writeImageFile() throw java.nio.BufferUnderflowException) (#1851) --- .../com/jme3/system/JmeDesktopSystem.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 0039f564298..215a301e738 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -40,7 +40,11 @@ import com.jme3.audio.openal.ALC; import com.jme3.audio.openal.EFX; import com.jme3.system.JmeContext.Type; +import com.jme3.texture.Image; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.Screenshots; +import jme3tools.converters.ImageToAwt; + import java.awt.EventQueue; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; @@ -80,7 +84,7 @@ private static BufferedImage verticalFlip(BufferedImage original) { AffineTransform tx = AffineTransform.getScaleInstance(1, -1); tx.translate(0, -original.getHeight()); AffineTransformOp transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); - BufferedImage awtImage = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_BGR); + BufferedImage awtImage = new BufferedImage(original.getWidth(), original.getHeight(), original.getType()); Graphics2D g2d = awtImage.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); @@ -88,22 +92,34 @@ private static BufferedImage verticalFlip(BufferedImage original) { g2d.dispose(); return awtImage; } - + + private static BufferedImage ensureOpaque(BufferedImage original) { + if (original.getTransparency() == BufferedImage.OPAQUE) + return original; + int w = original.getWidth(); + int h = original.getHeight(); + int[] pixels = new int[w * h]; + original.getRGB(0, 0, w, h, pixels, 0, w); + BufferedImage opaqueImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + opaqueImage.setRGB(0, 0, w, h, pixels, 0, w); + return opaqueImage; + } + @Override public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { - BufferedImage awtImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); - Screenshots.convertScreenShot2(imageData.asIntBuffer(), awtImage); + BufferedImage awtImage = ImageToAwt.convert(new Image(Image.Format.RGBA8, width, height, imageData, ColorSpace.Linear), false, true, 0); + awtImage = verticalFlip(awtImage); ImageWriter writer = ImageIO.getImageWritersByFormatName(format).next(); ImageWriteParam writeParam = writer.getDefaultWriteParam(); if (format.equals("jpg")) { + awtImage = ensureOpaque(awtImage); + JPEGImageWriteParam jpegParam = (JPEGImageWriteParam) writeParam; jpegParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); jpegParam.setCompressionQuality(0.95f); } - - awtImage = verticalFlip(awtImage); ImageOutputStream imgOut = new MemoryCacheImageOutputStream(outStream); writer.setOutput(imgOut); From 63a1d07535693cf124b4578524850b7ba06ca9ea Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 24 Sep 2022 12:08:26 -0700 Subject: [PATCH 029/140] jme3-jogg: upgrade the j-ogg-all library to v1.0.2 --- jme3-jogg/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-jogg/build.gradle b/jme3-jogg/build.gradle index aa99d11d930..c8cc0178e1d 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-all:1.0.1' + api 'com.github.stephengold:j-ogg-all:1.0.2' } From 6946cfd67136925ce1f9980cc8d96c22d88e5924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= Date: Sat, 8 Oct 2022 10:50:14 +0200 Subject: [PATCH 030/140] #1569 Fix license file to be better detected by GitHub (#1855) --- LICENSE | 29 ------------------ LICENSE.md | 30 +++++++++++++++++++ build.gradle | 2 +- ...nse.txt => source-file-header-template.txt | 0 4 files changed, 31 insertions(+), 30 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md rename license.txt => source-file-header-template.txt (100%) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a9e542c6bad..00000000000 --- a/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright (c) 2009-2022 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. \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000000..ac2b55fb2a0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,30 @@ +Copyright (c) 2009-2022 jMonkeyEngine. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder 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 HOLDER 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. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 842fb81aae2..c8adc8eb416 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { } // Set the license for IDEs that understand this -ext.license = file("$rootDir/license.txt") +ext.license = file("$rootDir/source-file-header-template.txt") apply plugin: 'base' apply plugin: 'com.github.spotbugs' diff --git a/license.txt b/source-file-header-template.txt similarity index 100% rename from license.txt rename to source-file-header-template.txt From 4fb95646d73dfa0f91deb588e24dee8462ad102d Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 14 Oct 2022 13:34:22 -0700 Subject: [PATCH 031/140] jme3-plugins: update the "gson" library to v2.9.1 --- jme3-plugins/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 17fcd7ab562..b88f0a64fda 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -11,6 +11,6 @@ sourceSets { dependencies { api project(':jme3-core') - api 'com.google.code.gson:gson:2.9.0' + api 'com.google.code.gson:gson:2.9.1' testRuntimeOnly project(':jme3-desktop') } From eff130a8f931d27512e048adde59caf5fc224ebe Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 14 Oct 2022 14:05:48 -0700 Subject: [PATCH 032/140] workflows/main.yml: update the "checkout" action to v3 --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ca2daaf4ef..5502368a294 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: steps: - name: Clone the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 - name: Validate the Gradle wrapper @@ -105,7 +105,7 @@ jobs: steps: - name: Clone the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 @@ -298,7 +298,7 @@ jobs: # We need to clone everything again for uploadToMaven.sh ... - name: Clone the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 From 8fe3577e1b6dd62b3a25ce1ed15708f018800291 Mon Sep 17 00:00:00 2001 From: Kasper Aaquist Johansen Date: Sat, 22 Oct 2022 15:07:05 +0200 Subject: [PATCH 033/140] fix: broken link in README.md (#1858) Signed-off-by: Kasper Aaquist Johansen Signed-off-by: Kasper Aaquist Johansen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8be3d3f5786..40c3b8e70f0 100644 --- a/README.md +++ b/README.md @@ -55,5 +55,5 @@ Read our [contribution guide](https://github.com/jMonkeyEngine/jmonkeyengine/blo ### License -[New BSD (3-clause) License](https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/LICENSE) +[New BSD (3-clause) License](https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/LICENSE.md) From 0f5f1a3a30b9fb824d25c1aacd971823626dd374 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 4 Nov 2022 03:40:03 -0700 Subject: [PATCH 034/140] workflows/main.yml: update wrapper-validation-action to v1.0.5 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5502368a294..a105405873b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -68,7 +68,7 @@ jobs: with: fetch-depth: 1 - name: Validate the Gradle wrapper - uses: gradle/wrapper-validation-action@v1.0.4 + uses: gradle/wrapper-validation-action@v1.0.5 - name: Build run: | ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ From 4f8fd428e08528dc542d553ffb7d32fa0509e9de Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 4 Nov 2022 04:30:03 -0700 Subject: [PATCH 035/140] update wrapper-validation-action to v1.0.5 (one more place) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a105405873b..a3b3f3d97f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -122,7 +122,7 @@ jobs: path: build/native - name: Validate the Gradle wrapper - uses: gradle/wrapper-validation-action@v1.0.4 + uses: gradle/wrapper-validation-action@v1.0.5 - name: Build Engine shell: bash run: | From 6eac311280a8be6c87465f781021cc62ec96013b Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sun, 6 Nov 2022 10:39:06 -0800 Subject: [PATCH 036/140] README.md: SDK v3.5.2 has now been published --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 40c3b8e70f0..b6f8bf12b6c 100644 --- a/README.md +++ b/README.md @@ -4,8 +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.5.2 is the latest stable version of the Engine. -v3.4 is the latest stable version of the jMonkeyEngine SDK. +v3.5.2 is the latest stable version of both the engine and the jMonkeyEngine SDK. The engine is used by several commercial game studios and computer-science courses. Here's a taste: From 116ee9eec96a47d8d339eb97df478d91e8e1f6bd Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 10 Nov 2022 19:15:06 -0800 Subject: [PATCH 037/140] Quaternion: javadoc --- jme3-core/src/main/java/com/jme3/math/Quaternion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1a1ab0732df..52247cd1d74 100644 --- a/jme3-core/src/main/java/com/jme3/math/Quaternion.java +++ b/jme3-core/src/main/java/com/jme3/math/Quaternion.java @@ -1326,7 +1326,7 @@ public Quaternion inverseLocal() { * Negates all 4 components. * * @deprecated The naming of this method doesn't follow convention. Please - * use {@link #normalizeLocal()} instead. + * use {@link #negateLocal()} instead. */ @Deprecated public void negate() { From 81c26e14142a7a8a404aa2c5f4b6b6749c6e8556 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Fri, 18 Nov 2022 10:02:29 +0330 Subject: [PATCH 038/140] Add instance culling function in InstancedGeometry (#1865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added a workaround to prevent shadow disappearing on instanced geometries away from camera by introducing an instance culling function on InstancedGeometry. There also is a default implementation provided. * Removed the bound-scale hack for shadow disappearing issue from DefaultInstanceCullingFunction. The “right” solution is to have it pay attention to the frustums of whatever shadow-casting lights are around… but anyway developers now can implement their own frustum culling however they like or even "unset" it to disable instance culling which should also resolve the shadow issue. * Remove unused imports. --- .../scene/instancing/InstancedGeometry.java | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 42251611b67..2f76a01861f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java @@ -58,11 +58,14 @@ import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.function.BiFunction; public class InstancedGeometry extends Geometry { private static final int INSTANCE_SIZE = 16; + private static BiFunction instanceCullingFunction = new DefaultInstanceCullingFunction(); + private VertexBuffer[] globalInstanceData; private VertexBuffer transformInstanceData; private Geometry[] geometries = new Geometry[1]; @@ -95,6 +98,22 @@ public InstancedGeometry(String name) { setMaxNumInstances(1); } + /** + * Set the function used for culling instances from being rendered. + * Default is {@link DefaultInstanceCullingFunction}. + */ + public static void setInstanceCullingFunction(BiFunction instanceCullingFunction) { + InstancedGeometry.instanceCullingFunction = instanceCullingFunction; + } + + /** + * @return The instance culling function or null if there isn't any. + * Default is {@link DefaultInstanceCullingFunction}. + */ + public static BiFunction getInstanceCullingFunction() { + return instanceCullingFunction; + } + /** * Global user specified per-instance data. * @@ -284,14 +303,9 @@ public void updateInstances() { } } - if (cam != null) { - BoundingVolume bv = geom.getWorldBound(); - int save = cam.getPlaneState(); - cam.setPlaneState(0); - FrustumIntersect intersect = cam.contains(bv); - cam.setPlaneState(save); - - if (intersect == FrustumIntersect.Outside) { + if (cam != null && instanceCullingFunction != null) { + boolean culled = instanceCullingFunction.apply(cam, geom); + if (culled) { numCulledGeometries++; continue; } @@ -458,4 +472,22 @@ protected void cleanup() { allInstanceData = null; geometries = null; } + + /** + * By default, it checks if geometry is in camera frustum and culls it + * if it is outside camera view. + */ + public static class DefaultInstanceCullingFunction implements BiFunction { + + @Override + public Boolean apply(Camera cam, Geometry geom) { + BoundingVolume bv = geom.getWorldBound(); + int save = cam.getPlaneState(); + cam.setPlaneState(0); + FrustumIntersect intersect = cam.contains(bv); + cam.setPlaneState(save); + + return intersect == FrustumIntersect.Outside; + } + } } From 930809e749e3e7841e0f874bdd6909c514703a10 Mon Sep 17 00:00:00 2001 From: Michael Zuegg Date: Fri, 25 Nov 2022 15:51:27 +0100 Subject: [PATCH 039/140] Fix: make the stencil test functions usable. (#1866) * Fix: make the stencil test functions usable. * Fix: formatting * Fix: formatting * Fix: formatting * Fix: copyright year --- .../java/com/jme3/material/RenderState.java | 90 ++++++++++++++++++- .../com/jme3/renderer/opengl/GLRenderer.java | 4 +- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java index fcbedab1ed3..9475307a59d 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -480,6 +480,10 @@ public enum StencilOperation { StencilOperation backStencilDepthPassOperation = StencilOperation.Keep; TestFunction frontStencilFunction = TestFunction.Always; TestFunction backStencilFunction = TestFunction.Always; + int frontStencilReference = 0; + int backStencilReference = 0; + int frontStencilMask = Integer.MAX_VALUE; + int backStencilMask = Integer.MAX_VALUE; int cachedHashCode = -1; BlendFunc sfactorRGB = BlendFunc.One; BlendFunc dfactorRGB = BlendFunc.One; @@ -508,6 +512,10 @@ public void write(JmeExporter ex) throws IOException { oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep); oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always); oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always); + oc.write(frontStencilReference, "frontStencilReference", 0); + oc.write(backStencilReference, "backStencilReference", 0); + oc.write(frontStencilMask, "frontStencilMask", Integer.MAX_VALUE); + oc.write(backStencilMask, "backStencilMask", Integer.MAX_VALUE); oc.write(blendEquation, "blendEquation", BlendEquation.Add); oc.write(blendEquationAlpha, "blendEquationAlpha", BlendEquationAlpha.InheritColor); oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual); @@ -551,6 +559,10 @@ public void read(JmeImporter im) throws IOException { backStencilDepthPassOperation = ic.readEnum("backStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep); frontStencilFunction = ic.readEnum("frontStencilFunction", TestFunction.class, TestFunction.Always); backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always); + frontStencilReference = ic.readInt("frontStencilReference", 0); + backStencilReference = ic.readInt("backStencilReference", 0); + frontStencilMask = ic.readInt("frontStencilMask", Integer.MAX_VALUE); + backStencilMask = ic.readInt("backStencilMask", Integer.MAX_VALUE); blendEquation = ic.readEnum("blendEquation", BlendEquation.class, BlendEquation.Add); blendEquationAlpha = ic.readEnum("blendEquationAlpha", BlendEquationAlpha.class, BlendEquationAlpha.InheritColor); depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual); @@ -695,6 +707,18 @@ public boolean equals(Object o) { if (backStencilFunction != rs.backStencilFunction) { return false; } + if (frontStencilMask != rs.frontStencilMask) { + return false; + } + if (backStencilMask != rs.backStencilMask) { + return false; + } + if (frontStencilReference != rs.frontStencilReference) { + return false; + } + if (backStencilReference != rs.backStencilReference) { + return false; + } } if(lineWidth != rs.lineWidth){ @@ -1128,6 +1152,70 @@ public TestFunction getBackStencilFunction() { return backStencilFunction; } + /** + * Sets the front stencil mask + * @param frontStencilMask + */ + public void setFrontStencilMask(int frontStencilMask) { + this.frontStencilMask = frontStencilMask; + } + + /** + * Sets the back stencil mask + * @param backStencilMask + */ + public void setBackStencilMask(int backStencilMask) { + this.backStencilMask = backStencilMask; + } + + /** + * Sets the front stencil reference + * @param frontStencilReference + */ + public void setFrontStencilReference(int frontStencilReference) { + this.frontStencilReference = frontStencilReference; + } + + /** + * Sets the back stencil reference + * @param backStencilReference + */ + public void setBackStencilReference(int backStencilReference) { + this.backStencilReference = backStencilReference; + } + + /** + * Returns the front stencil mask + * @return + */ + public int getFrontStencilMask() { + return frontStencilMask; + } + + /** + * Returns the front stencil reference + * @return + */ + public int getFrontStencilReference() { + return frontStencilReference; + } + + /** + * Returns the back stencil mask + * @return + */ + public int getBackStencilMask() { + return backStencilMask; + } + + /** + * Returns the back stencil reference + * @return + */ + public int getBackStencilReference() { + return backStencilReference; + } + /** * Retrieve the blend equation. * diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 6a6ada666c7..d14705d14d5 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -979,10 +979,10 @@ public void applyRenderState(RenderState state) { convertStencilOperation(state.getBackStencilDepthPassOperation())); gl.glStencilFuncSeparate(GL.GL_FRONT, convertTestFunction(state.getFrontStencilFunction()), - 0, Integer.MAX_VALUE); + state.getFrontStencilReference(), state.getFrontStencilMask()); gl.glStencilFuncSeparate(GL.GL_BACK, convertTestFunction(state.getBackStencilFunction()), - 0, Integer.MAX_VALUE); + state.getBackStencilReference(), state.getBackStencilMask()); } else { gl.glDisable(GL.GL_STENCIL_TEST); } From a2d639ca73a7344f1aa4b8b97e74a49af2da3887 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 26 Nov 2022 13:22:29 -0800 Subject: [PATCH 040/140] upgrade to Gradle v7.6 (for its Java 19 support) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 12 ++++++++---- gradlew.bat | 1 + 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index c8adc8eb416..2a7b4ed0c56 100644 --- a/build.gradle +++ b/build.gradle @@ -283,7 +283,7 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { //} wrapper { - gradleVersion = '7.5.1' + gradleVersion = '7.6' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36524 zcmZ6yQ*&aJ*i+pKn$=zKxk7ICNNX(G9gnUwow3iT2Ov?s|4Q$^qH|&1~>6K_f6Q@z)!W6o~05E1}7HS1}Bv=ef%?3Rc##Sb1)XzucCDxr#(Nfxotv ze%V_W`66|_=BK{+dN$WOZ#V$@kI(=7e7*Y3BMEum`h#%BJi{7P9=hz5ij2k_KbUm( zhz-iBt4RTzAPma)PhcHhjxYjxR6q^N4p+V6h&tZxbs!p4m8noJ?|i)9ATc@)IUzb~ zw2p)KDi7toTFgE%JA2d_9aWv7{xD{EzTGPb{V6+C=+O-u@I~*@9Q;(P9sE>h-v@&g ztSnY;?gI0q;XWPTrOm!4!5|uwJYJVPNluyu5}^SCc1ns-U#GrGqZ1B#qCcJbqoMAc zF$xB#F!(F?RcUqZtueR`*#i7DQ2CF?hhYV&goK!o`U?+H{F-15he}`xQ!)+H>0!QM z`)D&7s@{0}iVkz$(t{mqBKP?~W4b@KcuDglktFy&<2_z)F8Q~73;QcP`+pO=L}4yjlzNuLzuvnVAO``skBd=rV%VWQTd0x6_%ddY*G(AJt06`GHq zJVxl`G*RiYAeT=`Cf(SUN$kUEju!>SqwEd8RWUIk$|8A& zAvW|Uo<=TWC~u}V?SNFv`Fq9OeF_VpfyXHPIIay@Pu5J6$$pg{;xE9D7CROVYV>5c zv^IYXPo_Z4)bg5h?JSUX!K`q_u{>F%FzrG>*!Db_^7*7(F@f%i34Ps`JBAH6{s=ygSr^CVO)voP`v=SO z7v;4cFM_D>iVl{&X*N7pe4_^YKV%`5J774`5!DC}g;D@50h?VA!;fU1?Hf%%`N8R1 zSg@hZ8%Dq^eYV1!g8;`6vCSJoK+V1Q6N8ImtfE3iXs!s~B>js)sLHB9w$r+6Q>Oh#Ig&awvm%OBLg!7alaf}9Cuf;M4%Ig9 zx4K}IQfPr&u?k8xWp!wI4{CP#GTs#qR0b+G{&+=vL}I{b-Pha43^%8=K3997~* z>A|oxYE%Vo4~DiOih`87u|{8!Ql5|9Y+(ZY2nRP+oLdGErjV&YeVKw>A$JyPPAL+C zA36S!dNVf z;xJ)YR;^VPE1?`h-5>{~gwY2pY8RqhrsiIBmJ}n3G@Zs!!fD6y&KWPq&i8HEm*ZAx`G} zjq2CD5U==ID^we8k?=geue4Y>_+%u3$-TzVS6QMlb4NoS%_V>;E2hQ)+1Q@v(reC5 zLeK*f%%{PNO-mtrBVl|-!WaiKAkZv-?wnOwmZ=Tv57k=4PX=C?=I4V*THRFRE8a_{ zb>5YwDf4o>>$o{XYlLN{PZ^Ff?0FJl4>A9C-q9A$$&44l122Qsc|6Fd6aTam{=JO3 zBFfFe9seUPSUeyXQc*RA>2{WoKIYVltA&@5spdIW;rzOOqoQo`CN;~UNgU{{m9^c1 zTrN|8w_7+Nws4}Z-4eS9WMpF3h<@81a)oK9njh;-TB74vR;u{vE?>6FDG7<%GVXFL zUR9l{z*eEND6pp)+hpNT$VVM^Pw*S;#NrbCmH{dhBm?%6D|k)0C@Z9H>T|kby1^)# zOPmJ8Hq`8waoEK(9}IfP_q4yr(s?ME+T%UV-ikxW!XFb^6w02t30j$n_VSwevg;{9 zx0OXK_uGBFej=gbG>G^pEv^`I8&_a@t9>Nr;#r?XNKquD&Ho|`)qK6C^-7SCdo=S& z)vUi;m5*qIePEIbL=wJ|WCBNY;zCm2F-+@N2i{I^uR9UVZm$o`I|@<&2}w)C`h)vV zW{)yGJ3?GCZNtFe53Kb#uzrC7v-{JygKZUiXDV5mR z5la_vAFOvoh#yn)B`$^ZN*Dxp5Uo~_k8G9skn2)Tb>Kw#Vgxi`bti)^(z--X9F~oR zZ6=^_x@mDT~=h_@GGVcgBtLzssB1|Xy(xc(lUYJ#_ zgwc&ajE%^cCYW7d;xAxi{#LN*1}s>{K79MZrq!tYMpRA{T!#^tgXP=J5FvkbZ@gx~ ztq-E&c$`|KX8GS2a_voZHf=y8C{6~f~`DpC- zjQfrt2OGi-WGx}Y4>vM`8<4frU*!bq*NJ*Tyn0cqk=zpDdYth-PJIfz5>pLF@qnai zzj2FEhuOa-7$JR=U!L{UWWJBA%~SW-6Nh&3;<}iQO)DvOI&VKi1L8rmICePWqoY^F z-dC8X8~1T}=C9m&yb1kZzbKd2;29_Pm*Cs=y{Z06QZDlT7Poci>1@hFa%t0<`1()UTxcQ}e`fAh6K`<5C_SG`dw$IqzwEYNKvIH3VWlhz z_#^(T53W}jeWF#WIhj^U7AdIB~3feC--5iUiiT4Qyu81 z;Xa^8#~M@p%6B`LCKWWTa7I+35BLP=EOa&Gp2pbTWw5HOIjrx;2J(KI$$HT|w8}R-8fbp9sot&LiLs7ILlyZc8 zWbss7=*Ah|X$LEt1O|T?ABkIn-0NN`I8+ipfoBZcW>(WiaASG_khBtKM{hfkm5VBS zy0Q`4*G6HRRa#9G)10Ik3$C3|nQbFzmU-dA`LjKQY8icnx?2OE40%z852{OJH=?mbvwr9 zhlx0RDo^D;p*xKx?yT(`s7wj7BHA~rHF2yxnL<1PcU7FM57;?g^ z&CyPh9W4KvZ;T8w;AuNMn|nQ-xJ~CvVT7gAPAGi7w8udw_LOp+p4eZiI`JEC@Mq9F z#dA2AM_};CnL=y0#tZALdB(P~Rz*KqGqjwec%Fy?K(PGoO0tfskWw-aGhd7$ zTi~x1G>4h5q>ek=tIoT(VBQxrq)&#`_0UHC(j*ZO%%}%C)|EzTWEpvYDqCYXLexR9 zlww1ESB+IiO}=oq)8WZj%cY_FTQcEJ`JdABa=_S;O|kLhX*|5|D>0c{12DoC?K95f ztNxm(sTU6cWWd$tv`5X(=x?yAo)IYQ3G*2+o#|EfXko6erF;M4Pc;G0)pUDY)t`H9 z76Z8V9HqbWA@!`BelAT&ErrGTz7}%M*605PEY@3{gv+`yEhr{=EVp_tU%`b54Pn4a zz8nN7`eNx=*`f1t#^7>7G07IEnbnn&`RWZ}4Cp8W_DFDs-5)GU`bw}uBmOQfKmi2@ z(cWWmvHFTUNInRH!0y_ZtuI9Eh@O3+64wy-_2DF~E@KF3abM`0gC%|kHi@&hP_#B$ zLN{Z?$V_;+h?%2zEC{2ITyWOup*w*K?~vpwB(DX1i6oY+F)??;nyHpzaPLIt6G$4; z6>iAsB+&&NN0;ObWVOL+-^ZwD?nHgY>0k>0I3iA7o)f# zN&aX$lM@r_Iu|nSdPjoF{#QD9M6>|JSNPLxX^T2!jCKjS5mwNaO+SmBfOY z;6ZdwfzhO6Vs|9u81f4e%7*mU%8K>A7QWO0;QcX7W@|NSUVl)_>7VEf#&N6E~ zn9Wv88@Suo9P+M_G2(f+JFf#Q^GV#7QQ`qH#$N1y{A*_t^`5H1=V^u?Ec|EF6W+6B z(@Q8ChIUyq;+I5CmjEa1*v%d5{WHyhcHSjQuwzQq?;^BmfV#okq3v8bp7dBdk z54B+%D3=JWd-2w$)puXxZyZH>-$O-?tbSIlGc{em9xHN!44iaCr}6uZ^FpN7IvNh8 zbp!%4xR9np`>AOEd1e2_y}xW#v@@h3wYc?WiwL6Q>fxPQA81V^J)XtGs|Z&er6w~M z!1Ph~85TMG>R&ixNUnevc(w>fgb%+X#Wds6Yl+wH29aE%;RuDeZz5dEt%#p&2VK1n zKkqgl&*_YwnO%9`0<6MVP=O3{02EcR7PvvZPbL2KMuoRsU|Y%zw38qeOL#!YFp#_~+rtNJVl>lJSh_*B0A6n3XkE5po z9RpE_h=pnmDJFX*n6wmsWJ9GLu2=L8y!_R;;Aa2Jl|)I}Qff&`Fy@iOhop8>Y2{F} zbVk3rNMi$XX(q1JrgcIhC08@d5Zc>wLUL3wYm}hzS^!5d&Mec$Sp^$DUS1lD1>KAt z|Efof3nJ4^k(WKL_t-u8ud4L(t>q#9ECj?v#W~W#2zTt>|MCh&*H8Wh1_I&^2Li&M zq9j0`(zk~P7}dB`+15b*j%VPGr$;@4MBQ5AT>-y?0Fxfr2nC1kM2D(y7qMN+p-0yo zOlND}ImY;a_K$HZCrD=P{byToyC7*@;Y$v6wL!c*DfeH#$QS6|3)pJe68d>R#{zNn zB0r*Es<6^ZWeH`M)Cdoyz`@Z&Fu_^pu8*089j{gbbd!jV@s7`eI5_X5J3|poVGlq` zDo9}G;CsjW!hgN2O9=1|GpE;RpQvrBc+&dF)L>V&>9kd6^YIL?+*WDmcQlvwnq`Lf z&N$gF>3+E*NcJojXXI^}B(B-;@ebpVY}l#EcDWles7s;Ft+KZ@m+6FWaD^oYPBXVw z3sq|aKIDh1x5Ff=tW$(LO|!e&G?Xvh^H!GfiA(emluL!LmD=EV@|u|8S7w6ibUePJ z>{sOC6L27R+b&}e?VH;KvV3a;O3G=gwG}YzrkSTV6(&=;o)EV~2OD(Eh4mu@K0G)i z3#44IZhqN6+Hb2h#3R8YwJW7LesDA9=n)75u#46_ZmSh@6Q-4oHvGxFPY8x;Q+)d@ z*-SDqhVeyPGkoD)iq;z0r*M)IhY5I>gMA@RS&EIYPq}Z{$Q4Jbfd76EVhSF-sR^TO z!=o?>V(^bx!pG$26J~Z>Tvu&Uu+0;>m+pg(fmbu(97^(OHBH4;J8WIfv-f5}VP#VS z$Y$}SHKdphDUHlbdIVW!k$L6T{LY)|H}MT=l$22kIl>|46FK9dt$?3Fjk2RA-~AX7 z1|Xe`n)%h~e-O_qLpoFXJ$%gmocq`v0%hRw1k_6nh|+3pvJDy}m)V|xjL&!Z6?%pU z+m)r2*pWjEl!etAYxdzWb0{mGc;#$>rE%)b z@Rnj78P;$lrzY!XCa0&x+8a^YF*G|Q|C}bGeczz(5m_gq08wJHIH`WqHH?A}!~_3{ zQEvMXmL<*nThl^pL58nbHgQ1n9cYmN{C8J^6AKS%?~>1DCt70Q2Vp0;E@`GF%Tzkc zSUt&LJ=wHI6@#8_%=2s=j^4VBd1-h_)3 zeozYua!|{x(qk#z;tavf28rj_5Oen-cYG%;R6I}Hz$yMXeg^)_$OUUXx1r^qrl!DG zYXkAXKBMrVM-rJwAo<5J{NW1XJhW;Nh*&`nFV-Z;Vd({KSkMxV#cn|bXJ z50GtvFE##sqGhV#lv2s6?^yeBShlhR%XaPIo)iXOue}jwZ;Zq#dgDn8H?74Y+$Z?C z2Y5mCC66>dp%sVMecUzCirWq99Ea(TDwClZxtEB~4N-2JmlH#>Z2jOcaNaw4tn?P->BBGNHxUHez7>C@TZNT5Z zHerlG0a4~06L%>tn!~$s^L5`~{ueLZ5?`$46nHvwKxM0V9VQ(k{A40xDVw{+Qt)RV zQ)T2Df)cp0nv!lUFt3D=i~k!V|7dUjpz?K2ZiynO)$d{2*YT$N^CQ{t=luZ>WcE!> zg25p}If9RTho%G@PZp;5zBwv`n+e9iO=6dx1V^|4Ty%`oE=f7O&QC^s!4MJ+lMG>^ za!mgpz*^SHT+M_zm;{H#E~SaU^Kn*y)nTAF*2@t5mF+l)bte+a+goaA*zXJ4P)H|y z{4OwbJnIPtMp4E~=64gM-Y{#o{x)+8YCg$C7Yy=;9hdyBgRFIY2_L9DL3*B@%$5#m z8P}+)glf*}UPD$C;_yntx}9VPmSSnY9`Thd09nfoR;3`kar*FRfS)`+as*t2l*USWgmaZ!qFubr1DegTGZspyYMgic{inI0dSt+rJR z((jjMrdq^?VSZ8FCO;0NW@>O_b67gDHP%W*^O?J z91NQ7ZFODMSvHj3cvT#6RJUF7x=-BJFQ^6<&mOd15Z&M!?b+3Tg!UcgldD9tOAt5K z3X>MlE-a=sj;K&}sSng48jQ7sp|&u3;@e>V4Cuf(!s@9lZ0Cg^DKWmki%>$<85tOG zU;e{%zHU~KREBUg?FbcseK{lmK-`*S1p9j_4hF=F$y)NB;HsHwuf_A0Zhy395eU7o8^A zi2t7Ch|KVprUn03N0T2XshT!g$HTErcQBBG=TWaHkYtaI2CJY7ajI%yr&9 zVC^zJ3WW03bjwGNx{l}#+D&Ml_uI4PQhV}qZPXOP7ffSv(O;hX{Ff1|HoA~v)V!4y{CdALyi2YPjrRVmRYilRv z5PSkj*Z_8Fa*sCqGN?7YTnkr9=i9X`qcw7nqz#{bj?B7NiV9fWF+%~Rb1X@MuS^Mw zC)d#K{(-9!?xStM2K5x%x~ogWxgIK>s5r_RT1jU_lxdTtIEFWvi4eJSAiGec&HXQ( z5t7!J1b#SL|8s4)u147PWQUq_e33!5Z#f$Ja&az)(Htl`Z0@Ez)0d74BzNHHfH|<-8q*ZMf?%eJzoGS!0S6Y zSU7y^1+;V$Je9F027>1eN#_tz+2t}Y^N zYfi9}J!N^SU1CYoNBDbD39@84xLroY@0f%%c^(5CE+}!b5-Mt3oXe2nBdyicgGIL+rzTTKv`}Pp%fG1f^s?sgNH8=Q}s4Z>0ZCZ8ZYF z4og8nK%OA~zZMJX01uFtrmwhcgg*XbiMP9kfkPYFASbp7*Bk^5ZBzV)dL)JhPwDkM zkgdHeKw)orJcj4^)a^wQC2|->G=OBzuc-SskRrrf+H-E%HQ==Ex}d*504#GbIUXIB zcZs@Oo0i61MG}&0bu%@2N?MMJMRXyTVb8@3wF5eY3G6-1NdT~{{~YFs8f&SNebdaq zKmP>XqCQ@iaamuvY2m%xJ~gdSLSj~DBhB`NCj_c}NbSjB{r(E`_-+6a#vx*|S>-GU zHsw^dxxu`e)q1HbH==rLFap?cebKumnTo=iJQ zJD1#=o>0%Y@&jP?^)Q5bTV!pzrf=FoHq2c_59pq@my{D4AW8VU*7LVp;LF-qESV;L zClRfyQ6CcD$sd84K@e@p_ALH%j(Pz@Em@QFyY`AG&(|!(cG8!oV#ejr`y(LolX}Iu zL$)G)8^y4sUAYCWprzVR?`#OJ%NU)9U^B!OGSj>Ly;<)<(nNh`?z*GvJ|ZBKfZ`0 z=q_yGHWPp~R+J+{{@APVwmp8`=%N!L7AT^l^oaM|JrCFu7J#@frf=z(vGq2>sQ^@u zk=^d#gDf}ME!~9PaLfw44~rsG!)T7h8~dY^VcZQa+ueWPGG$mWXB|H2$$0BT(QAIu|=DJXPQDNes3Q>-|Mh=Ih zy{WR)QmhL5rQbBYPBa+e7)8Vo;_aKrg`}izmN>#ATuSDu!QUFA zsgM|Kv@W(S}Ag^6e8)9pQc@JLj_2ZIkO=8)#ARm#mU=NncWbmd-SbO;ad=y|k`shy3b z*8o0@EJo3b$#zSgmnlT7KAp)U!qI2M`hiC@Gp0)pNGHYMe1$MBNE}Hd{Sv^`wI7>MzNwgVv1ZzL zttmyv!=TKuPH$b>r7$lgP5?vho;#Ks4+zLzaz-1b{p-Fn6dWy1Agg7O2{&VQ5@s3A zAqzC9QokRD59!@ex#k>xy61kq6h~O$lb;lB;Q|chv&wzR+N zgXdIo%?q1Y$TzsdCo+n$^NODN7yd}cAv+rkG|u-(wTp?zUSUxaA-W3dwqikdrokwz) z68)Gn$Nwc1zB$F9`#(af|C3v;|2$bo7fU8f7h^NK6h&@xi2m`)g4mW$?l@5JEc*VV z6d67@Fl2w6mO;MYUl2U>R996gQUX$d>$D>)TNGq*arz}f21yh^uvIM!3u$H{_CH5! zrjt9L^&J8UqEV_lLn&}nc|Q=MDei6t=vL_>X-i8B%f5FDi)|qQ;2V-T!qOi*uqq{U zElET6#2cb>Z_6p_vw44&mN!;T&~ubi&p`XGepCNAfa0-T zC84V@VN^R6%z({m=$%iXrbiggxvMiBpww~ktD&=9-JPK3kPCOGCJNQj8+l9k#!QeS zv3h$Ej>@j<-zBW0Qr`5tNQVRfYK_$3>nWUzf&c*tCpl@aYwa%b;JNeTX10OevcxY7 zqnLgKU-X9G8~&?Dr)`*7GryqhN#;9v`D_c=_xBcD{j-cLop~pSnM?&7HggX6gb++ftBq$idM1|>5t+68sWf{ixREbMkZesmpjJsAFPQ#2+8Uek z$BPbu3cQuNDQq+^M}&ZuSHjxUgxOjF<^%4 z*8lc$CgA<$n=DYg_DsrHB7zYM0Ro|gS8ZnUq$u3GQ+{owv9RdB$wG%d-;R+I>?i?b z+r_mu{IL6WTYftdz?0#pbHkmQP31LvXcMK6;mAP+;q^L@q}v~TD}Ni>f7@QYcbM!T zX5kShHv3X1U=>B!2*si9=AEJCBt~GIH7DL4^+gHj+q}tk0F_?Q-=z{JY%77nkw>$F zG}6ROaL_)3t$jX=ZtFG{Q=LZfNjNb2LK=m9l|7iaB++N|S$vAr1 z_gf3JpIB|?dptfQ{sOZGlhyj~D;T#hjaNh0X5(o&7)87^t@@Hteh{0DOM{tCu$l#& z&NhA&V4VR}nzZP{7i(5bGB17<7bu+RJ1}k}=ffSg%=+213Oy@Aj1vv2U>U>8tRhKM z=*e<21)u6SSb{CC&We%#6X@duqLWGJ>O)Ls`uM98``34g11;D}*7>c3+^c|Os&;t}`(BWMD zfbyr~$j%{6%DZ`kR-}s~p?0#&-5a}b?6tDqwtqY%ep0ypSRIB54G@|0J5E#LkxQk# z_&xE=d(U}q?*Rh7L7f8AM5{qdGpC<&t~9YI!%j2G@nUPoLPSiWHjCVP{JAe?cBjQ zTqI=R{nv5c@|R)8Oi3cTL{&6%XdTgDP4CNYT}q2f5|Xf_hID#;83kd+v0RRyNKYn} zyPahwd=4ncDORLvatBc~KzT+jiiD{tzd3d*T(f7ayS;J&I1X!xaL2~POrw2ST=Pr5 zu*c}fb@)0P6jv))kNl38C7gmnWGmlL@{PWOVYt9se*cS0w#@W=N+dY#V08ci=Zmg9 z+${f#Qfs5)hOPxC;q{(J{Kx4HF)2QMzlVtXz0-O&h2$VxtT;ROvZ13nN{IG>Asv{% zHuDqgZ{R2(X*hkO+!HYHHWvRYrvN9fl-1?x6b)oseZY)@dQ6O>9Y#8*23~%bzN~Nf zpHGMdS-G|%F^v3Gnlsc$s4Wl=ZEu+J6y~*Ih2tpmHfO56JXKjldm$BxDvW6ZH>JrU zdRo}=^466lAq6!qY_@nQ}5ETUEoF;`>7b8W910_Z17!r`D?QNvC z+WF%@IkPi43n4;0Ks`M{x*0-^GK7oCAp?pFK1`~RoMSe@jAlV8vQruCUNyQ_7wk?` zSKe*|!4ar@VSA}!ThlIB*Qa5){pu&HS!a)-{lWL2@o1486ZK_!!}FSZ>vyUPIOX#+ z5d3~J24Op?!f!oNytub~egnkB`}h?eh!QyX6&^LbNuA#9vH#N_7IL|#6kIDhLL=be zEg3Cwmw{A(cm{&T zPg>XIWX24$Mj_#^k2I91C@h;b$8WNVr&MLjEwgAUtSeJ2W0)6Fit}PF!K&1j=*+6g zL{XOUrqhNyPLemIF4C&hThR8fie9^fYg$yl$m!1|YgcPlO>TB-(X{lkN~X}R=GA!Q zou<9ZJV6*}SN_4WRsqzRGI&p$;9DxDFTlyPw6Q9rlo@E3tMN&Wo4eFs{1=RCUij$V z`8)kmh0fhTTiEyvRl90B%q2(Moh$jg7{NeQiy> ze!H{zbG7<3BcK}XE&V_1kFfGA7D^ODxn*@nqlp!{LhYb47zIUlV^m+7kZh^a7L1^D zvI?m^9PECMnnN$0hi^Ur0b-~QgEORanrv|`dd;ek$4rAgEEof3HyvuYoZ)H*;+TgO z8CJY~4YDI^7RD7O)m&2h2K`-4e-I$1zcZ*K>Cd7~sSxEXc{d7-;f z5Ykr56Nkie%=z4_LIA}H>c81e$%ey=2hjqzTxoO0MDe!J&PE@EmX49jQJJg?HNw;B zHRHr)3do7CGDa3lPAZ4LAnpT)spnk8(ZiFz$|F$1m*A@!qCPug>Isp|MPI24i>jp~ z((9EQ9W#Rz)0AYT&ZWOWKBNtdNYYm2QytK$o-_|W5j7Abr&73(MG+Ar4K!Ij=nKu# z;SNkveY?Oc!I|Vta2{rb@c50#p_byn|_tu>Pv}6YDydl|}X#4oZW2 zvq)Y@8iG5@6c3?uu4vdLSBq23P&qUSvtGcu_qgH*?KfaT)@QueLx6apA97FI7sXP=foe zmrEu7;%Z=yTTGUsHsjR(wU54xNPI$hLFZUOwh=uhZ&rLammOQ?w*)}?Ah#%&K~OZc zl#Owj1OCEeXt!ALV7LgJ=MVbCo}<%92WX$wCS~Ins}%5+sb*C{WoOT5*2%sgjya;~ z|A#;k?j~J9qB)Tku1BGX=MrZ}<%Z4}i$OvCHv_3vtH_NZoK zjJljjt(~Yh%aI@gFnM*e*@_*N190p^@w5?SjRMb66N_^3EZ#Yoh<8FM>Yx$+mTbp$ zjQQS7(rs2j^54CJXdkH|$1&$wPOGDvm^@1o1pl9~!5&B+I=U-f_M-M&r3zfp2%TH%Ib3lz-^t)+Z9E+>W1Bt1`B}rZ$hZ3{0n|nZKM9O z$?_1+y}fB2$zEzE$zC#46=0E_4x7-VXY5}<+d!g2+Kg$gvU-Xm-A9DBZz+bZ*zDTx z$Wfb93))oLQf;wKi5JBJ%$yq}m42lacy`bC9PjFg*}pCnqn@dv{k9WiwCC07;6n#e zJ499v3YGQ^WyYY=x*s`q*;@R_ai1NKNA}<6=F8IvJArr{-YbdY#{l1K{(4l$7^7We zo~>}l=+L8IJ`BhgR&b$J3hW!ljy5F`+4NA06g$&4oC-`oGb@e5aw-1dSDL}GOnUuy z)z1W)8W9t(7w%OCn_~#0;^F)xic6It5)3h);vuLAKFS4b)G;Z$n-R&{b6h@yGxGo> zT-cq0W7~n+qN10;1OS+*c>H$(GoKq4hGG% zL&XJG$PDQ6K^BD#s_MsnlGPE+$W^B`&a+Z+4;`*nyKil99^E(wW?t>#V_xYWHLl2} zIV`uiR-__g+<&m#Z*4E|wjKY1R2mCm%k2ayMSDw`Rz_KA!3P$uIbB`dl`3&A zmT@gMT@ZpAxBys8zRtgoH+ebSaVA)maP?G1=G4x^Nw3mV0?qehWL35vMI~p$y0hGL z6@vHf-50P~uoe6yY&*D)Ekmi06LF!Jqz9#7kMvWexYMbAn{}`{3ZBsd6$5jBCujDp z<0N?b*1%T<-_Nxh`lKtla|FFqs7RZMtjHAwZ0Ck&s{x`#^S?36BNQN1JU^0f&TRoC z$}c)LW7)-n$CmAg&n(96AycC4!4_*D(~HvXyLW>HORuI0;ny$f9h{!Ud0=X0x%{l6NH$ z?lttWn}DQL521;-r~Kf$N_YPo)7H>3gI@Ivt}GnR=8W~Nn7_PE_3{sRNn`R~bs`g1 zoTh`7o4H*TRp7VBp=%>&t&Cd*Ny~@;{C)P;62d^dipuJYUV3-Dh<#a&AIxtrmX42( zYEH-8F3|^nY-=yw(?^d!hTojNxr~A!n$Ao+2mq*kZ&>Zm+BDC*sul=~!LUtWiokIB zxc(dNwyk&5o;>WRt)Q-Wj;fvuvJO&DLPe%mt@t!Oq^VsoIN0iTh%fh#`-{Ha?a8gf zj^yA3`=_NEONO0Z?}YVP*dL{T}v|A&cE7$_0G=g;1s*WDQuRcq>cJ?z=8b5&i<)=3ELSW%Kff zs=my9Q%8?aMxZeDq=RBHg*&HnIeQ_}X@oh=f#?C^HSg?1dwLn#wu(o^uANrRZD;H; zYbOec$#wJB(u?w22{gV+zb~pv|Ag!q$N@^|6n+FV5-X=lR$jajjeRh$1tjht$URz1 zhw)(ksAr2;QBXH9T#A$6V4PsR7K)){JQb?79o6&*IwDPZknNqySIa6pwcs)~xN81I zKc-GmzZ$i(8RaU==$Dx{tD@4nph-V*=W{Ln97*VEN^F+u0!F<%$l=K`ikIp#<^Yt} z{rx1gk>;rVccPIo6hD=xPQ$PxVwl6Cl;YI6iLf3!aevhsyXXZovK#TOv0|*T+^ii5 z+YO`u(SO3@ybv-DG)w)E;@+ULoj_+<;mc#iW8{9Y!99vE`HdAK=Utac&Eq1uy!TLgOS-C1E90Am)B{Tiw z$>$Er{s{snLEaO5@u&zqxE@v;p6D&?u@40t{#VNA&7SZael};kGEwnHgD4V5RNM@g z(EL~B=A8&?pPPW-fTja0Oi6SVtI_(3ME!qWLg-uK2afWhBn(C2PAmUyu^2h?Y402i z9P03g5$1#etGdUUo?#skjQ|$*()ybRGMXM`-2?jjThnTcPV==7sg$k{GxYdF+S*zz z%dtBo(R9!7SW6Utq|wFpsKMSAH-x{WB|Cz62A8!p8!kHz1tM=9I=M&xqQG zz17xBW7t?Q?C%@4YC`p*za(>hOrK&ELyDQu{5ACOg9noZS1SGh{-FcLy_W;nf$N`N zGYxdIzy7mL3K@Kw65DmvPH0@&;T{y&jP^AsaYENi}q|A z3}l}5V?z_VvpHf%CkpN@IK`czOuLPY=yBUf8Q3b9$X|kEiYROV$`T8T7ZjFPvKhbK zDYxzz99JRNzsx0f1Y>IrIQq9o+W(TsB(ZtN@4*)DMGr3?4~Jt|37IBI|7oQknQI3X zAWs`45xiCHga9;8+W{|!Yy>tic?%SNq=3EX@z2Mk!P0dKG0NCHNz0*F-a z`7K?6d*D4ri*=>wyQyQt{_t=t95*gB1|tdTg45fR{KmKD|3ZuM$QlkX{-tUkq@3Qd z-6X|jEyZa@tuxB}qrdlJdc0{8``%3M$xl8$9pUzkFa$Ww{Jocp9>;5~oNC8o`3GK& zy7_X8YoQDCO1TU_a%#Q+rC?Rr`r)W8CdpEe=>uMYDx6^46V_1DthgX`6CnF*E+%bY z=GYih(DizXEVFDuQRPQY&dc2p;Pwo7L{I2r3;QV8IEPg1McP{PchEUDf} zbtSAoBMPt?&Q@{fG_3a7gzHl58O7e(h_F6^rKgU=a&(^WpgH3U%`tpj3CMVRA-uol z(hA)(VF{4@`k@PREUQJ_8w6CcMW4Pm06{fw^*>aMH%#ik6lD{{j~nT}Vw=wZ(;Ct& zi1nt}RmOGrVHP++5;Z@eE*lkdw~?>AJL_Yg!~p*adS_s1`_oT1B26S zt&1-4twO45pMl<5B9T;SLH9Q?E>dBXcy@5k-{YQ5K!A`=YMYMlLOYc(+LdC<@@UIZ zxq%vI<;6P)=W4nRb7nxQ9KGzXsOjWs_3V-2*V+r}?dAZA7{7f*>^PxEw|6+WS0wAs zen2zj2cFKIr`~Ai`YU|OR4%DQw8uM=|g2B{;1Ho`mx@??e)rX!p$MSlA70pKVcvZ@|fYLpEV~s7G z>#?88yv{ekJpeJL<-?FY7wf10XpS{B4}jy{uc)7esm&J1)ZYt5LI_{)0BkN8Nc}ep zg%SYD0Cub3?KXLY*-dYntrghE|}%?RY5i3yVcPFlheiJUMLIr=Xp=U-^siywr8MF^JAEwl2uQ$VIfuDFPisd}4W2ZxY$C`2`tBTA~ zG2P62@*~(9gYmO6#Ya<1TG#3rQd0BwVyNP@Ayt7B(h%z<@N>Iz;|2VkT8T3`anW@3 z03^F>TCLS9Y*sY)#=BX5!LYD9Z;z4QSOL2^Zw~0e;OutRfp)Xu83Yz~srLh8rR}fp z=#yHH{&=!mHgDg!b;9K@Ux99VmQ*K2Xn%gV6YWHHw(<_uA&($p}$2U2TIs7y+ zM7X5Yk#^wpDE4kQZmN3&VC{!nno7wD2`bEeAwS;W6>$oUt#~E57Imre?b54{c$`tHdB6GMC`IZWLL(%j20Bh zW@}9_@4EsYT$u1Q3ZPWkvYxUX{6AcsV{;{1w60^@wv!dJW7}rOw!LE8wrwXJr(>&Q z+xFe(e7mP=RLy@dYSfEoS{pC8KXH4kGf zd``z`=z(*mSdLiXj&Y{>&akI{IMzo@tD>a^<(r*Ssf6Nz;ZsaLra9mcD`MN8$2`!w zj#+BZCrV}b_c=qEqt7{oF$>wI5*0B0kP{DNQ5_-V9dZ<9u;vm!(L2I_#p*nprX%tU z!{;Gb7IuVBg7pdB2!{X!ZgHqp5+?drImJ(UE6~P2|C?+`E9th5QSv!}?=L}=tvcFMQuyE`=pek1zbRxBAFdgqqB#0~EkA_CpTe0`e$i(eyMD!C!D0SjSaixQMIl zQ>-Dj?K($9qMGwhRqIt28n$`*FH_6v*JjZRnIMxz-qVe_KzSGY5Ph0$(^e$r-hLD4T4m@eV#69bG7_fQ>o`!yu97p=$)>fb; z&!>)wS*Fj!ag#iKWRWiC735;`@XxXFT)nniSe~^1r0v?bQ6_Fokmx~(-O5D{7$d>R z#Us$PxL8^}t1rpnJ@#E}+O?`@a4wB;n{#!lX6WlOwo}C3TgP%?N=BT*FrxR=JR(g$ zJn3EhTI~xj_mVxhFImqt22JE`CI;B~Pb~*cFE>{uL*2mnfeKb_aYO6sDC{Khp%ba`v>+M4WqY2KK4@w{=P~Tzx42!1yHniJT#~*CHF5|TVC_n_ z&;r3b9d!f0;?+iQ8rT1N>MM-D(HQrU-WWU9=w|>nbeG#luD0;ayPj`4=&7Ik$Z{Z3~ z!oob~d$cMHx9;vjAfJ{XC6R@pzkLW4q1ak{?IimWUVBKithq`vKQD14&60gGKCCale{X}Ft0By269l*P6r zuTm0E33lN!&zezRh=5l@mQP_RAR5sr^}&4j;(eFAj2@K*7>|(4IdGb4yB%g88|TKZ z^M@nOtS|f?{!z}s#}S=w{R0`LbVP{k5xhlw?;F>N1tIByWsnp`Bg)hb4sZR>Y12=3 z!#Anh?EEZFm==f$1I@Zw1Y6-%6aE;!l&t#!4vB-%4AfB{X;!sT(jBKx*-5qZn|89Z zK%Is6JLf#w>eauBET9VUE&>aD*^+~!ilaiM?p&mM&kqY3D1*5QUGBbUOI)=eY1dMv zJ=ybPA_VaWPE1+MDhiYq4$DfAeVIv!IP-*#v53?V-c^a) zG6p$+O#_1{V`nNcS`{^%iBn8Oi4fO$#Q7x-$tp2dRs-etYmui-mt@P{hh?ldJJP!? z`!i88d>h`9rIRd6=^pZVuo5}3zUbAX>~uzA4C%servKlplCW0(Ta+B&Eey1CQ5DDV zf2Mk*YRAVjE>){hi_9poOCsx=BU4gQV)kovP|^v!npW_>^LFUzYHx;MKo!BEj7Xy9Xg-A6>kWs*$)aMAWh^_0Fnx;eR|2;L0ZjLl*+F1Moh4?D&8h6H6jJQ+OxgwJV51#)zSmqvRnQ5 zz~62JXPCCiwK9W;yo9-%7Xka%OtQeVDK5SGr51}$q@i)OE>BHgfOFiV%SZ5E(VC*q zYujoHFnnF^qs^WhZG}uBRIs4{4xGP&Tbtr=RJ?=4?;IaVA9Yzp!}H z9QDT#L{7Y?)r=m^ucWOjUuJh*FSmqL?!<1x{iOcP?l7BCorp91#(gUNGIQf@1)d1lXx(RAI zhm*TFNYgXZn_A}FPfh;WMHE%oCs8d+1emobQCt@YTjxcWoK81LeXY~+9)^+UOmeCk z)#LMg9G1`jWr;WZrrR$Gwve9&X+lKpB~*OkxAEnRpO&^BwsOm&TDeQBlvTv^nuju5 zyB8jH2{_Xtz=1n}8hD4nhhZvyxynbGz%2iKM-8|$N`wX8O-Toi=&@x087+joKHd4@ zsx+@?mPB(R?mMWCIeejm^dhs63ARzdm}jsA(O)QqT|m}QRWm-(Hzh#M1)wVV%1iJL zg(a=;b~-ZkGDk#mk1~G*z!7zGrRGL-8}=VILi|%;0knSAjJX1jZXYa@^cU6K|NAIP zkrpm_?r8?!`$D^>c>@hwX{b1l4f&cY;wwU&Q2vPM9oGB`Uj2&haf>bY84LFfn>4P} zUwt~VVTwui2oj$uGt#`OH>|MYjm8`R#n z{C%^u?$@fW&NV}iCuMF`&DU3gT0TNA(vM@&mV$M7yWD^p3 zN996Z8he29k4NFCg+9PbnZ$<&>5-W0fbtK7!ePTkfP37tvtUFQiW$|1%XoEZO`#0Q z2^XjxY40!DruxCn-p%m|j1RfInIaROco}Cf&3zhkkBHj&Rt=WZ_VkNJdliOb-H{>p z4n>c+XW~q#1M6<*boFS%=vdUE3ndU*iM+EFUvAM1=)%}A49e~^iF9Tr^(nqF(J^n~ z49*I<-WXCZ`1EG0hYOd%nsoM{LT8_q$a&QSBz;#S3YCwj?)0mjn_saa@O3c^sMqwF z!ZcWHQHCT~S|SVe5eVTt=z64&T=nI)wG<+4e2@}Gp9#uWEM+p-{L1PUC zM9N-bN73qWRRpT*YCLuK_D+uRgFcwsV}^odrD$A zI~cJDK#5qb8UPL(A_=P(=)Z0U`Aq`WLGuPhE^-isi?g-0`OZ?4kK^MyAsY+mxqt5G z-B14#h=^(sGv*CF8}cd}Xwl*_z1KEt!uP`_(wPBT8=FmK<+VOOk}fZ4Gj*{W-MSmu zygps+?d@%?tx#Fn|0(KF86C^QEgcz^1&!sUz|u||p8_`(gR(h#GELI8FrjSjfNCc zYJ9BHx9555<@$3ttNMYtIMa?NQe?V&_luijx2?!gBJ8tg}l4R@z5x73q4 zfZVtX0lZOzVV%@yTg!w5oMcYuMfGrD!RFwqChHhY`G22|vNLn!6a7VRi4gD!@Ae2K zT6A|%SwkYp{k$!ki4db&5nZ!Hg{8dj)h57Z<$r$9=s?;uzmx54DcKt)m0_ow(XjO@ z{}vbrW9)Fk2;8-9>tkzX!IEOW7lMb$gf~wwZgu2{whBB$YvW7BQSPQZQDy~)5Wh@8*P!VrB-YNi~zFb27ia7UtoAd`4C|JS~iU%&Qw1UMjN zC(CRqwMFj@{DT5Q%Z!g{RpCq?CpzVQqdKjxHQ1xa=u_EKr1ec5)TH;7hvWIn?hs@&K~48_$RK3+ zdu{2({Eh&7HD%B{)|+9CYaV^V1<$`JDFoj0UB!kwzCp*vlO(9kJe-Iv4aj7J^fJER zTEQS`H@RGhfs9w?M)S`;LliZ`Qvu3g2?r)nr?wT^cRJy(wBCr0MDqtRFHm$E%-!6g zMLRw$2+YPDN~0`{Vm}H&to@Nr&fF{~L0>m}Ghn>Vj81s`EIQnE@l@Jse`#}N0!!DL zkzs?x4I;fLH-LS+=E9Vl88}Td=@l&5&xyb1KaYf^1>c=cC+$#bcr7(`-gQsjD7Tws zxszZy^8Sv(2%nbY|4UVV<}>Y_l1lTjrKy;Y5${ej*V%OT0+D~Ec3-9;X zs?8%af6+X@s}jQO+NREG?W&1rhl(x1!Yfpt@?JLkH~UV_9l*DG6qvuakx_O+bAq=s z({A;t{jPMtJAA3|O@KE~J3M!)@g5`5KHrMBrNC_Vh4B|&pimlm=+i4!K-R<3m20bD zzS$Ki+QfH%hnUo)1S~{GWomug`!{WD(v+ zuvqIy(f7nrv3AgZ=8rf6?es-84@=OK6qbY0wJ-G zL(2?kPhb zZ{|(D3#69jUn8s@S7FY>F%&HMCc-%c24`6k2TkwB}T>7a66k$Rk>2x3dp&D-EP;6vCr%iE>GKFx;(izH3Le$SQsp0A%5 zm-Se9<@jb?{00JSx_;^KuDtmei!?oLZDoJ59(**b_6Y`2ZP$kvK4#2^Lk;B5oCirY zRlPg?{iEPr_J_ES2=O`sJ_qloEFsXBDQ+Z4sZubH45vc)72Y|~@)oVTzXL$U?w#*n zclYx8f%j*|f#eOo&_;}Am3`vA@XpB}-9L>H4kiQkO%r&~{%W@YWSeD_%B5+F67d*j z?Utu*W~cd#8x`Co76I~a0hZ}GzEOX;;hDT#z2m$G4zcHYIefxJIe3HizO!1pDziPE z*|lfM&rHZW`dhSY#7rpieqo!w>m&7!e)!(++5So5!vv0pL0Wxlkw z;_!rN(U5yR9=>CNO_J%S#)QEl@X^i< z$-v~-byW{BRXav4GT1VHt3jrFK9-@DZunt&iHnR->YIe?0!h%8oHlN&$VawG{+?<< zoY3lysffn`42Anr(od87p_%kBvtEl~1Jq51oU>0Cs?E%&n0t{t#)ExsgW$H{YuO*? z(`4X_deFhMU*%36&*Y&?o78sAOZl$&98gl@b9zEa>Ul`Eht&~4&@b1AzPD7{!Ati$ zwXVr7)>u0Sv&p#{4{|Qcx56H> zF?_X1-NV9Zi{jD!EQY!op(nLS=XU(DmJtXhf;wDL&4dvd`O>zAaBzN(?%law3sn1p z_#_Z!M+Gw0@Qk>REY&5+l&ECBG20Y4{6#618u0a_FxP38r-^@-!(PFvJl*UdjdBDn z11S4BYW3AgDE#Gc`TX_x<1XiTCER)+z?$_X z7n&6Ev$hKOggBsrg&CpBUpqPE1~%I*WKQW)@&B^`ZW5)SBHYAX27S#;6vo)8c5BcH z!iREPvmG%-xk%IahqAZVSke7KH%Rm!>V_tpH`>bSS4Y|tT-m!g!=Ni9VbK>Rx}WE8 z1ss1w(!|#dy?b|&w)Q0+&&lInD4O`WjJ{*tN3GHw8{8SD?rdB!ZRgxa1F<=81)1({ z2JvQ>m?i8VI<$}9MmtE)MyKN(H%%Ec)=3jmP)K#QS&7qL0o;%>!jhlVO3 z&jsJtdo5DnGgt&A^6{Y8a8ne9+lmC2B)oq7mWC?KoKbd`r)Uj|vMQx$o%)qPrk?b_ zW1Nh}Mw*Y_&LN|blw(R7 zFqMcuihIjBcSQDyLEoxd@%w52JEp%6+H?S#HPt_I1T@F@jW@935OmoG zE^SH~5V5=!n&E+yvOEFgM<8j%Fift}(j53d3V%1r9NT`}I%2p0$%QVx!#G2{NyO0x+|GF&XFcta601En$nx7I1 zQqAX}hG!*oND@sdrvXZQ=WU5MOE7QtKbgX45%?B?waqj`sNjDd- zUTH|{!iKvo{j~L-X=^?Us9D+2O!SG>$w%in^7zGGy+BMpnFr)#L4Zc0>7HJeEGS(u z(RiPD!>0L<(^-m_3%r!)MMdobk+T+6rOX^H>@PRjP^E3Fvx;U$0pz%a=(m-W6LZ}U zX2QnW7lPQm!-pgsRh$Rxq+tS|LfE_T9hZ*a3%%5EE8!rlmCi9s zC%T&Q39zQ(krY&I&{y3pYWA%5nHIL{j;9dmcaU{*@}l1i1fbF-HD&(6I+spEHr?l5 z6XUR+=CRY)I%wupKQI4-`6@A*Z2p1C5}Q+EOD4Yb@LB`10Ghl=YqM}RO`lWgijdXcY?-_PlpTe z5*pPp$8~kOI0r-}EJwDCeZBX!`~Vja_Xl`%VEZe$l0N#Q`pQFV5Kk9_nkJD}iNtEl z0C^Kr-ATPgZ(oeg!%ExcVXg|I_d=BoM=ZHAT`5PDZJr04Ur3RdN~zCSJui+P?cOm? zZ_4uvSbO6q9^3ohA?X&NT{--uRs)j1^n_QP0Q$3&rxFIzTz7O`nX?jRXhg1DeB#5) z(GfV1DF?0?JQ|Qk@MriD8NQBaWeKv2Q%Q{4hBkh-u_vne>zF%J~@`u;J25*=?$ zdhu8F1#*^Vel)g8@`n!4w}b9O5MZ9mGr6l(IoOWq9%{A1u0kLk75}< z&VTouJCQe<1WILdAsGA2MManwFz@+UBd8q0t~Z?>7i9wlMSc4rIngyRBL7^uYc7hA zBHUFVhg$Uoyx@ss=>vt^E5y7o;$7KRvv{t|CpAnB&qk`W5$c_mfC9N(b79uh8{1b@ z`%f{Lmb-*Z{$${zz}Myib@*kI7yMEizc6;Irq>h1)$KEnLBTf!E}{B15VVoV)p+aT z76}rh#zlkeIT-ez_6b@mR`!5_WT}T{kciOQ8yX_<@OT6_PmxrmJyWnWqxT>-Aho3b*pIl1(z(06k|pbILiK8h1e<%dkjsXB~8Vf{m4 z;ClZn{kzSkl4$w-j^Qx`(3BIce`g>_bgmJy8*cgJ=8Ty6LZs*o(tJ?TUi$1Et5WlE zPm1hE>IZ@-G>o3sf#8sEAr@8W4+aYgQTPkDDhUV$hNQpvpEmwC*qRWQY}4A92_0DZ zmPs>)&dZ8l5)X-zicS159QB4{Zwz=3=NVHv+vF*NB9 z1yz|msvE4PVio9vx4?D z{ZQdbB!aR@k>T3)149tjYac!k9CIDV$2WZDZLI0o-b>X4G9HSuePIX}6fDMrw_{k4w^WTJKctikHje-7u zn7gF^^f9vkrII_IBPZA9zyVn%O~I^a3h^!RY1?E;v_(46klc%M2I=TV%+aGbx1n_|{GwNit$QzspH)ZRKc+9Ky0a-Mj~~W; z9=1QW{@mQWZ0CL4h$4e)g#u@U;Tecj_=E}U`TnGM7>o{0dU4MT*|8>hhQ`?UB!zFB>>~9<{V@O>aC9U~Une3IWIR5R z_5_;sDvxI0ns0l_QeF?}X5QNM`1(*9drDI7dr~8llWtCKyo`HdZv%?+Yo+%2`Fb=5 zKSVr%FvKu>!KA)Y5&sPD zuJbS|=5`k){vruC`iTofuv9tp)kTGFd-$o@dfQ&XgVVImF;1#Xx#`I3vul#F$qWYb z%LOU(SbQDVH4RnT>9}Wa7hO`?yKvd%M<7B)^-9gvI0d9NpIMkS zRT00KAyowFDZ=SlDLo`s`r?978R0T>hJCU9`HXoWFBuyu7Ifhz-OU9hFUQuonGfWr zokmWPK)otgYn@!v?`Dtcubl8K1%*k2j$mrp>~SkW z=^_So$+T1|P2fC#QyVCNlVUHq?y@pBngYPoosbeTuE5F>N&Y)$kL=WDpkyH~cO!1J zMU8RHS*10ceS^H7l>?Ax-ySAEq;fFak>8M}foyYCs-;Rmzg$T;k1$Bi^ZQD=+=cv~ zbPGjC8@KD2%G>R7`kXxj(wO;v?YYy^+8h$cQIphb3NS8{p_AkYO+3 z@r-QEvcg|3shClf+$g=3b_M|nrQ|lu+E$yX&=MQ;_k3cF{6!0wx6Dg;;-oBc9EN>k zD#NH0R)&||qCZOZwIv9erOFWBUabK&8^iW^&#Oat0LxZ=F3cTrBau=&v4cK^>5k@gj#zWtyXj%YL_X!h>bYx@JNuVPpBwJE56w;HXl zZ1;k@d>8+2?a%T+rZv`KSlm|ckXJH62?JJAR z7ldHyEgPiZ7!yX$7!&3vTs-Y7hkx;Id(DrB6cEMyABU(*M((X7YWt-L#i`S$!5}fl zC#oXNEBbfMF4HSLYC0$tY1Q-u&Ykz7^Eumbt#?%(T*Y>yC7L`~p}oAkt~tH*7e4Q& z$EWB(at2C8c9em~sOw`1CvA#}IOF9Z2~%FBmb4G8IYeC!Dm&P!zH#Jna-NO;Qd{(7 zATVoYNg}*h`Jn02H$^WRu1L+psWjwYMr~!BZZ{afjMr|Rh^JQYjck*m8ZE0?)~vqw zSAykMDOKwNT}~IGR-3e435!bEmBPlvKn{**+>sru9y;ynv+RdQX`cNo_%uiQyM~gY zkNXTcZ~J38fc(I+Tg@T>ta#K|CyTKv73iu?Y3>J!+07C?lcTyZWvw|?(w33jJN{5- zynWxvFsqw231<32Aj^xVe zS{qBm^{P2re~|C%4rPHF|F>PqE#D4Gqy(PQqW(YSb36aV+ngr7;Z^rsa`1CFOVGl|5mBdB0*q*?%XBXPjPm^A~cwh}`D~ z?6gO&d^<6m>+l5?;>v6BSph|=1uthK(GEITC3RddQQ6I%I8e=$ZwLj#N5a1>8ivCg zc9PxY9k%zK80_2>^XcdCV4!Dqbplas_v^F62wKZCbfyb7Wbkyg+t5R?jVp_p=87)rAsVG;p?@}0DhfjF2KY=ur_sDRN5Z@ zBoczZ8+*l`4CNsWF7`5M9V-hSSKJz^0xO62%BvUldB37t{XX4Ba8~4nB7(_iRUV7C zZ;UVO848`?$wGFpL>#F1+QXS!7Eecu#h!577tuSg z6^-(>A_N+VK1MVMP=Fhb(cBTDWU#U9m4gz0I*3`Ekeu#d_-kiPg!qv3`67kym=Gc@ z4AmeEJ6{D5GT9l)0Nt?D)UZ!J6$_sfK%VCX&4dy{lH3oNgOFQ2La|}=(_+;?BPZhJ zbklwJ?_h@!#;1t8lY{2DbWMd63lRBe~A zUI018Hx{L;2 zP!4pmu_b}ynHxga0}8?m18nj=$kLnve9s^Ie^-H@{|7@7h%5N$^Is(t_dm!303><- zFJ^N8IbO0tDI&&}NbSz6da0ByoGx4z$_S2h1eJKQLn#puSq70^es*d-_l4(XJ#*_n zK*J}P(truL6NXuaq7uz`1IeN|p&1V&u2eyhN#=m1r|%dhlWusBQB&9Kj?1K#Hhvs^ z-dw2ubqArME!@rtqD~^LMn}(jgSFkP6{lq?QJpdKZ;mfckF6(uBjSn{+8(#`kG@;n zm3xcjQ0qycjaDG+MetaBT!=+z$|gzdx#dMIAswr_Th_kYiKDKk!&_UmUaRf(O6SR6 zzMcwVclitdu{K&Gt?B%0$DH%Ka)m`JL6Z#Jpcu<41@jFbBz1!FpuJbOJ)Z8kHKT}Q z_!}IRR?c>0&Nt&Qj;h!jwPEdQD`+lYT-#aWIWB5Cq~_MoaCWl~Jf%0pW3b z-Ku(nGC90fjj`rXh7Cc(Xf)$}yt?d+VM=r=6)FS@`OQ&6LV5%jY**8LDEo=q2-2;W zXLFz5Yj$C0KPF35%Za62bizyq5V&Un=D1ejqYy`jNUkEZx`7gG{jZU)SoHqE-`bUo zsxgy5URx|pOM9qlM|Bp2^+Otw#8?sx1ynFD)OACtwIT+Y1B}#snwfkd`ZNWUuZ1Dg z3J5J&JYAt6fN_#GTqdGv#wb8&nj)t%)0R_2(EHvf6Pta)r*dD@@=u{net~%WnTTt@ zjak199mId#cZ9@4m$bZo{wloNngnd}jm87j!n|hi9Gq)eq)1}J2NY6a=#-LWMACKc?Fn0eJgkvFVwzHPJSCda^P{jTCuDdIo7gYl<=sY)}+_Q3T%^*<8y46+?f*t zH^<~z8%7i-y{g&sZx`Wx(?%_9eB=1?F3Q=~ZWpcXS2{)%Z9?Cz?VlQHnd}xq*zI2y zC9dbVFHaskv)NGv?a~q}@_}vlro>|<@v`XmF4Xxq2O;^%wnr{e?a?y4zMGVO?J%x^ zqr6{Bq#9Sdib%!nZ>kG=6?f%d7)P_OZ)Dq)iWU>+(HwnZ2ea?AwD@Sgm6u&|?0uVx zHxW#~O1#4B=U!!E>x~yKjHM?d#H@c!rP-Zxm{VDkNw8W`WrERLYXUVKYIYoFqPj*A zFD}v?HkI1j_Hx{o@ika5m+~!ax#-9xYI>XIWkO7@)a8b3_C=V??O4fZ7soW&yvXmK z-Ps1%D+Tf_>unWrYEhe=B?nJ0+0j#f@%V`N7WrAJ=nVTZJE zu||VpNVe*I9}B7xo>6jqrpD3elbe=GMt4c$PzD=N*o1C^{TEqP{ol-`R~MW*V!kQ% zn+%OSPE%}dn?Wye?nKP0-xm5TJ80J_9&2daEWBpADhIPefDBt{al>tbKt)<2snTIu zZ=8K+!iMD>YoHCf*0G)b%;7n6H#1R~!v@As4^5D1lst)5TM3#`b+OnbI8 ze2bnPSnwdjYL}M91Q_*VgiH&E$IwTZ8S_za4*+yAgj5BfnG{is4=6UmO(6JZKUR5SgyC~B8+P%s38NFVIE@Q6rfXPzmilun?o|)VM7f+` zBdcF#M3FbOR$Q@j4_G#;NQenj3gRkK>d0ZD3{BN3G>@?AF2^t#o1j%e<=&-KcS+6# zm6Eq30rjfpO$--s?Bj7Y=s=H~<(V?^04ns*QVD^CIxlO0hb~rThyP*JH%;Os3o-J4%j@DjkQ* zLeNu35%fvejsqOEvSa^M)%+~Sb>V1HspK+y1Fw_zI1{Y*=POV}KhLx<6ibQ~4s47T z9GzXb!%Psmx}s#;glavT22gg7+Otqq7wiTH1hgtBRnI*GQ#>D9U4?Q(U=8Ef&r_)N z0=gyY`$sC*AdM`2lT31sy!%Z?Ys5TOU?=+5bRrov=-JL8B#s+Yvyd!I7ej~T!?yqB z0G*_hL^v2o@bg96In$!D)){V8(7HmoIrS38vkt=Hk`(G)a-;#YyjiDcdB0a)e+l(c zZm;JipJkXo>r!!n|Drb)#WeSzW$q%|2m4c~$7Z)uqb+w8Cuw%9_w^&^?xo*ck_nj3 z@uxkG#F&A0mw=OGT>nKcYT1XP=j~}ze zn><9CpZC;te(7Psr&pm%h}d%@$tGvUmk74-*flv?d+qOAVh6;i))(ag1T^!K6{7w~ue z!|EGUtV7CwfxW&=hxs>+K1hz!@B+U!ly3QxjW>KHQcY2c$WirWOqv|mZz>>sCYc8( zb%Zcz*FDj9+sw}1&G{$)chro>?Mq@q&LmDOu;2mtO(FN?UjNt5^ovxp;t5fo@QHzU z;@Re6YR|x?3ORQ%4G;Mm9#`^!7H|`;Xumbak->7ftC1n_fQOOC(Y%4vPXoHvvjLG> zc8D~=@;n6U(W)GDu&xX|!V_A-YIzVVtZDOu0=ci9mBwRhz zFqbia8@GeR7L*&w&8f2`d^!*4v5n9uA^pY1j~onD8Uz=Xti(&Y5Vt=jP7-gF6G4=5qf>o$TuBF<{bDQW z0b?DoR%bxUoO?s<1AS5!>{}@}*5I}_zrca*l2lfIwAeWp8$3sC3 ztEe~-=&EHrxI++EdY}cv7fZKqiMa;iYSBl>2Oym1mZ4f5e0y;F2GSZMs^!hUS$x*a z2x9lgyVN0Mf+2;s^Orv`y{3ztYA$?w2dJ!1D4*;^h;JGzMmFu3ry}jIu)6VTR`}{ypXCA07t@KT>O#Gs%@vd7>me@^RA7eN=#Q>CzXb-L%&MZzWdOV}12D8!Qm# z!NxL)Cak9k8f)TR!7r3e|{Z$-S|MS9FN8DrR3$qkh}! z<`ucgSNcmAQP!FnVJ+dIMQmR>##46@b&ruT(WY`9yt%YXg3x?K^J#|)6Kj>n_;2)0 zm3y_Qk*;Ud)nT%?iqrJm(>i>`eX-3+%cjK$o3rJfDbTKEad5T1T|O7#9NrqHu~rmt zN#ozS^(SDrA zsv(RB8@C1~R?f8Zekms{TPVD5IM3Z5td7{^#dnE0>oo=gjzot0pc|W2-CS6Sq_xY2 zKMDYyz&m62bzH&UjDIx#Y3dY%4v<=hB-68UFkV`UdO2n=$ z#L&BUcq-2)V8}*ybjF?kFjFJjt1T<@KGe!$-^(q=N1LgKCHaX=4v=|7;o~<0rzSEhRMu+*`oOKW z5?SX<;N?sF@l6-Kc}=7kTvS>_d~#^UkwD#!5W!16`VLA}O#fomaSk+2EKlne)J(XWzpHxYn7?p-1nR=c# zTBjb)7n*)FYNEN|o3!YkmYQ&hI$^e|!bc*!!0>rekNz!DNYZ#$6A^S^LvoH_P$Rlp7@a zv#OyyvAiwaMX5Am9pv?V@u_5A0mA!KU|3&r8 zpROC7?dY#2mr0fJZOR46^c1;}+FVaQ9q~Ysb}-iX@Fj05!hZBw3NZdz=k&|W(w7ht zbW%mADXI^t)}f#^V80V&k3;4+rO}GH9b8#W9#VgsSAjF*maJdH`dPzgJo81_2Xj6B zJ?M*!zA#+fIE5N^f$!-N9dpW~a%ubr zd_d2GxJYsVk4Ts)vAZiCi+n{SDW=MO5zSQ=ui$AD&S~!p9(aku@VF^KE&Dp%D0f|I?$O6l|8FC5g+$-iz8m9mo|L&C8{W5`2ds*u}tmk?Njg-NH$ zuYOT^Z6+X4k3hP4;z6TETdvNR=lR#Nrl9yIl_xy=)8Zrf?T?DGarFi;1Ez}5*}eDF z*k0GJ++IymAM%H#tFlzTmafY98Ox-XcLSY8SwvFPht`ItUu$z4q86N?zTuX>LiAb= zlK=f#yCxc&orpOyjF0y`XPSLU#kcRfrbv8KNQJvbMg)Z051D(nq^I#O+N~k_rE3^b z7d~@V=<*_xEmBf5X;pk)FMi%&)Db#b=!dc5kMQgRc5;-gb;nNfstPyH)^Ix8@L!5{ zlF1VP3$6U7zVU~d<_qiWn#c2qxq?4l>5EY05pwrj9OV5a;9Pd1I5*(JJPX!(wjzNZ ztk+_oHW*koHw&sj%v}q8^&1R8`YYHU@|{TOdBLH70I};=UY@EUkS01XT#dOHO5)we zAg~vu^3FrMVKr&i1H#u2m-wJuqWB1}w_x5H(JExSxDp4Qq{9U}k>OtiWp+5U@H6vL zBilZ%XL1Ifs^Mk%ad$;&xX#5S+!T>@H@Oek$1*TUQ21Cg<@w+eVAbh%`sIUJ;&s28 z&b|j-P)*TP#fmBIGS^y9D=0=;SE@SUw34e=<)|rOh7_X)eQ7I@l7#=2=zL~?Q_zyY-NH*)p__8 zXl=T?l&$Mk;T~zeH{2`IHP5}e<7FBv*>4~b*qco{T4Fe{QmTwndm8vgt**DfC7CYj^x4(3e#4BnUZyCm>k zsypku(lIZ7|KRtdLkDg0(`D|@fP#}ehZPFpUFrPB%_3QBQU4Pv^DH7{W{U;8ceoPy zV~^F5{ZZp<93x z9h#!%4@8_||RJ`FEIb~EFW}a)A)E--&5iii? z%}-rwtJHPYM=>hb??##Q1)hIGlDOZ+-FDeHJ%>og3OCN~H?Z~H=Cn>dYeGTf&^G!HJ;=j{ObHef}gi_Ld zJJ5hmjNqRtez^0*hgfd>{R0Zxyw&rJ0*4)#u8s9yzg-C?d25;-n4+(`D1;FQ>!(sUC3!(_REC? zbP^_^zyPg9hK;2vAV8PR6|A__<*1qLq6$Eq8l4S6miweXq5?a-nHN^HdIY!f_-o@u zp>Y<5g14Q{Vq)T-cj+<(iSIn49(9+qkL2C3?9iuc1&4aE89IqL*f&6a^^zfQ!1XvI zfXQM>34_t9t82$vL;XRil9PbsK+TGPzDy#&S3cjbOdEm~NI6t9>84uAq4u_*#>l9q z>VI>bQwUr-2dEYXydv#&S)X**ktfYGV57CIm05Omhc}Jl(!cnjYr1cFV7GftkGncB z&Hn2ZS{d3RwD9IFW43<+gepDlSxb;sKMd4%92<=IMHrjqXOhMtmgBT~)AzY1_Q_Nj zw@j(JDHekRvv=jqG7SP@l9|N~)7YfFU*pUw<#ReCAH21<$J61cB~wM-4wnZuf?!x8 z&@&FDqPxuKW1#{Qs|nwITE(P<^g=KYP1JZt=8t1#dyQx~P)ChKLSV$ir527yem+}C z&!-)ct4_`<5j}3Z5e_5){UC0`%OIs5&V!TEOyxa5zGJiDegY_wdbk620d=Q*!#?^i z2(l5VjooD9Z%&w*U%NHIDy}RGVS6`mlYp4y-LVW1;yhH5ADCa|jvjb^77b)wd5-wz zEa)Y94>QRui~kZH!G|4I!~88=%0&5G0eO<-nmHrap#K1XR^grjSe|Z|icAjz75nrP zACVIcUvi7-|NNp!+-;Hwr2EQhS0&}q%-04`%he-MLZ%u)DE3(ue zxb}WfOasYLv|TI5YXcSpqy`fNgeG}+nlPF93JI91>1BvY--xvJTv2LSv#U(gM20pcy6m*!qT-REi98kj;igw`RKd( zC~Lj(W4oNOhm!qSdy9MN+v(nUxk~==dUOJzzjMH4O1xV@F(@m5V@h|b4a{J?WriGBkzCCt>v1AD;OO~ud zS+hiL*0B>p#vMeuS<-!EH+B=*GRP8IgoH@h#@K0WF;|rG%kOEr_vJO6f6jBx^PclP zbLRXpXXg8SK7qpH#M2sM(~zwCG;wtNyn?vMWGJEWiqBj0IAtfzk9VBXz_y~AHU6~9 zecjKYtN>+acdRx@uVVO?`NcJ&LhT1VM{@&HtRG3?=|2^Z60B~K*p@boc23}r-TbaD z!>XBP(u5m`S#SH_8J3gct?H5V^cvy_&#begx)Yl6h2xK*oRO@Z_Bk#4%g%EXE^a;b zkdlQ0F~ST`@j9*Ukp#&{yF1LU&!?+q4-voEIiw6U1cY^&#p3_)YP{yLY(Agqbw4*} z8(ZHtUQ70I_%0rD;mz}WmdC+0xKo3QFeYCmLt{d-lfmT;q-hFyBwF=F%k9>_`t!PruazqK8B3CmUW_dDa zB)FO$wiBn55}KS%KJ)C|1^w#z0|)Q6S9)z{ffONO7hcJN5)R|W9vdu zoyY?Fc{jh}d(4(E0)-LvT6x;Xw+t|wZ!NgmE6k&T#;PUpagBt@kH>C#&)1QC7t?o_ zAGL6{))=~`ebD+i!0lx%G|ZSqFsmA;M>fkEdtL1C89?>1IG+_kb(Cs5{gGC1!-(ON zM}(4=p|PQTfWwU^_usPnyyi7ADZw^bJ=~J+bw8SzTDySd=E@>hxg8&3{L`~}(y3Z% zTbEOv62Z1^`_1$_4C`-6(Z~G7_vh=SAG#x|65B2UCPq!?^i5{&D_Tm_eSWw1uIHig zn@TUk&u!KYG7rm4?ApX8yR0$1&ey!0O9w)5rKNLOWZR)+LC!X^mE!XjZypOQMFo== zmvnO_yf}T-26K4YI!MOfmLivK-8F#=<~6fxyZh< zDenbKj-#aen^9$u0nf~#{nX>NLw5e4-uETs@zK<|UKD6Yl2Ed0Icys!G>* z`dZe_AfCIqLx1P1+N6?X{7YMGtt7VEB{zz~#I=XoGkH}LvBRHap207-`iz$gn{&4{ zh&b+cohV1@otped*^G;Fg|p-3hRt5gX+$C`FV>nOxo6+yY`w>cwW2^NMP27@_Lw}y zeaVVqMbe^?%#osXsOgU-hFW-hvZ9_)GLOA;>wpBC`+#W8jq)h_D@5#SkY(|uF!^Be zvpDxpLH;k;0&3`IV|#nk1OM7EvmXh2`2Dis?iDd54f*uw}jI5THWNIpIqj#NNJ0^2-^Wl*XFz;=xU8n9fv&FLCRIMSj7Q{ZWQ@hZc50(s; z3m6Qr;uqSO66T^?IXs83+G)5t6Sk}PG{2s=Wk-sPcMR5+`7w%`ajV|Oy3(43TSu+C zM~-Zmxa(}^%;=3m237SDD%R~xy8}xO5~CNQrV)Ltrk&z;N6jZt9)3}| z@p0saOnkL#elg?UO_@Ig`wP$CW^}0K&8wf#eIy++_>C90jd2LruH+s%w`}ihw92os zil}cNBDANCIN?G$uC+&?1()6!CWQzL*!D=s5W4p6HKG=QYwh{gCf&{3AST zrcNN5Ph~ju9%GXq_H!sthKqWX%||#6QQ)I!eFR95MgKL%q5H-4IkR`d3zHeeKHiFy z(u>-81|;aIADIjbIk)%244uctVlG#1_LwwztihjJ%A5%KqOMyC2rvu|l#eN|91lN5 z=Nt%}c-$Ej=SrDJCxNO7n}28o!M0qw?(~+_vJ6vZYt6Tye z6T%7!VXP5SO7V$#{fL1jMC{}K@z(d_t)^>op*uwbQ*~aco^uJ0YYm$`n&-3CT0M4^ zFXv+7eDBVP03x6O-dE>vRE;nbk$iI7r0?Z}g>Ni#E!lJJj2W&fiz6x=Nh+D04r|@# zfX;@vAkD%`Z1>BilpnVOI0lkfdtaiv2ozv;#fqmZm`>4^9_7-NWrc7gB~{=VO0r|6 zi%rTpc9bR18A3{*7gMjq+3UOVpKWMM)QH+;&%Km}>K;^!mqB|X7TOYb9#>(mT>XWq4gBjFX0woPN(1n^o!XP zq~rFHG`l8OKHGr&=M^G~PMXO+(xsUFhg$FK8?}<)`m7;V2eyLo#pS zkX&aXT3)!$R%e?x&V7=z5>efncx|Ql+l*CJ5z3#j#p$}#Gqc4tP0QJgNXW1p`S}VFsL_g(d*5kcnN{R|e&8PrW zKTs&SOM>;#Ax#=6M1~6G&d35Z&T2GJkrEZ6pOpa)9IJjGsXzsSkdS{BB;hyeOv! zKFJJDEwaGMyunY48gwI|%#ti{pmXrs)Mit$ZQHhO+qP}J;Tzko*tRRSU9oMal2ljs=<)aX`hJabHP3$5o@<>0 z+y`6!4c0*S13}rfE2|m?1cU(-1cWwa-VZZH@dqxz8+{Dp8!E4*e5J^>D2lW|f-j0x zo<(~QnFNO1pI8`Gd=Dh1B^mL?ab$;(Lh-=8JXtcDpd5?J1y(UPr2%wU(aZOC<-9lL zfcxF*)xE2UIN)87z5VfIhVHN5;|_d+;QhP>h}{S&#GHB~#GGp3!G^1MJbr%lo)4`o zc_%nvPRltX1nccyRLGDVhDq}twP!iOEwD#^U`j(>W|X!^l(A2Bq}thVpjupbJb$tJs_GSbRy=NhT>;2vm1Jp_7P7}k!J11JV$6$a@ojwipW`qx8>vXJJ zJ?zdA<96Wd;j-7&y8wUZb`0vX<7W{%()c?7O2Z!-sp^ecl~$6a?0}R|mAP(@jFxjh zIhxOTBZ1C!Nb1X5dw}fW(aiP!kXA5QDScnJ7E8 zW{-~6^Pn2k&Fjj}2Ckjx{MvEXtEAXY>rYahfIyx>Hw5VZ;Rj7GOVwBeZnpy+Dv>P! zGjqds6s?W0{q=I8gany>eP?xNX%WZKX==PuvH9xy+WvMz8S6wDjx)_Zewge9Gq_0k zEAWR=HIJ|Z#=i8{dR{C6TMglt_Hv?R_Lr}FzoWzvzrxeTP*T{hrUn}X4n&;~;bm)n zhjTJA;7Z3(7NN6M_mgz4;=Ac5MkX47SN*K1*q|LqUH{umM_55_r&15}m{Drjev2>) zSD%5XQJ(QP3Kf{R!Uun#|9FREeI%^-Jz|lJy~g+~DJU z@}jhnz%n*4U3{jH#O4aLo;oZ~;-*?!?e`q^m&_*lUsR@Vuugr{mlw7#;AMPBJq!28 zFJVD=aoQsXXU9xeE7pV7LVn#q{p!VZ3%Y7}jE47Oc_kZjN{$2I_Ih`Hid_gb!z77k zLEPp?R;<|(jHShvV>3q;6{-VZbkCCwhse5}9x5_xyKM(xnjv^V-XBsASA(EHumh^r zu4uRPY+C7=BU8QW{OGSZAfm^B!Ait0-jY>*sG>$R-+;7@n-8id2AU2mHkJf0=Ox7L z3wA>N`?)k>o~;OBOg*l9-c&2Ax>sd#(g1YY--PWe-tT@R^ihOGFOUaF!s{7t|8@Ch z_a_pXzZ3hE9!TK$1W#azp-gEOQ-WuU#0`utpn2;A8trA^l6q$YQF51^@s+gh=n(ox zoxo50I#y^dUD+qqZWwdRChW+6_RmN-hX4{Bk=n^oC1Z8WWcqd|_FqA#1Txzjttspk z$qnVX*9wL95^mN zFaghCQlK}=ONlTTi^uzFqhx1MtD@5q52vJ+NFxQ!u7FgleEERVM{9Q0KxyV+k(#!U zjP{AHSQz$~(Idp)Q>buZc_HZTh*;6r2LVj?1C+I;u46gWXMuJCdyY<=&+h zm4(^0&>UeXB@WOkTUHnuLdRJ}V^~#YwH&^#l%E<;i*sXUO>N1{m4ma@FJx=_#Nw;< z>DuvrnXPe9bTKX@WWBobWN|7oK=)Lm*uH{jQz)jjk}-j>shi7zn|@FwV-hX@U0v25h!EE-T`2>;fbnoybY~s9BLR+`KF%Q zDzbQ>Qv(mtg1L{<#PeylU~f84G=c~OVgw9kph^bB%mbG$j0Gi*<7%^`biLCi$6A3Ua2o<@&WZB%x_Qab`4f8RYu2zo&RGMRxDj1!RG($dfM3s(BZguTy zLQ~Oa_37Ex6x&lHa@^$nGLNS@^H2-MXqXBgn+7g$+NPHtFwcLI4Xtep*>ku19Ga^p zp#I$0_;mELs}quj#0<%t{k44%{7sS|V3?G1-3ZXqJ$R|-W>adjIc-=-Eg~5@2km53 z@Xnl(UkDbZjcc2EDxRKDmzlg3g;+`NXn<32Cs&Gr8M9>iNKNBkYED;3NV$c>%@2(7 zGuZSz;-4HW^C9IKoKie9{tDcJelMU3LgIin!vgno;{>zF^|F}Zn0+;$q2u1o;iwNQ z*ah^oyIql#CiRE(k02Ch-UkgWPBjjbKsFW>pRn$MumX$j zqFLTNU8r{i;*{D$hD+hOUa3_r7*l8 zv!m^zk9RI`jl^J^vt>t_yJad>q#1C=@BvNJ3MPiI931*tyGN(dfE8@a@$)+PFz%6ktHtd^7EFEspL&_D^Xzo&X6_DQ78wf zz1psXF}CZ($`6(2F%C09Pw5W0$pQWGyoi+#B$=AsBzZ;_@JF(*yWu_ba8?#NS)qv3 zq)8|X$tO8<*Cm-6pLzt=@HH~~Whyl@SnX7DTU)W*f~rdggk(W%Z<}b!YT6ltALyJV z&W{eSCYIj#IUky_2kCU`3+UF0CXWJ{R8hft0T~UY^%aGF@Oo1BC3Im`#{kkc7=7sS z8CyJwKM+!`5Ng(Bjw7C=YqBjR4pZ2q^G&dX1t1Bk9B9@gNUD)hE_4oC1LkMMj*Bml z!1|Cs$=oA49A5dB(J*y(pS)A`;qu&G&y}CmAx;G$aS6rh0|Wz#;j$XWiYE!A`t z-nl(heIYdB4%$A?#G8lH%12=MhxWT30nM>+I;h~}7?yr1=LE_C8i57|Wo6{sNQ^>; z76_DvAknlKbXXCYyWKW}OVJIAO$mR9f1kA z`gr)*`~ttfA25CqYm&2*ElP{2i^7qjnqohhLcekYd2ZllD!}7e;-T;lQF}5|iT6py z$l_@r6W(PRz>DAk+cMkZ60X498M-8S!#MJ%S_YjdN(}{_^tcey;R#>;6?L~{leV>u zPbWCJT!zM&*IJeiG+#{cHEvY+ z+Lzy+60#``hEJ4SM{BO+Om>~)RW=p6jE0QoZkC2X1^f$hGAhP8_=LV(#|^Z~1k`J`5Y4{&kph&!7&$xsda&#_|163LJY#sev-!dySjv~soVP|ZwnwS8hqE7eW=?jZIr zi|q0V2R4CbUK!WWlN?7FFNm=IV8vl((EGk<62$xUXcUio))$cnA|RzW;>9U(Bnp6*3SvPm@L)RUplH%j@jDW74248VZ*?j*TrNov+S$c>Dg~fOE1Sik8ABjAeJthLGdbJHnAQl>~+P~ z#8EO}Y7Or4mzgHx>OH=BF}4#ZoI}bJDIC?5J}a%Y(U;mvo%ZW1r2&8f2;ee-6!*6Q zFsae|^`2GCb)p)TzZ{-!^I1Vp@Gyr_M=`Yr)@w?iR~9Kw1~6sAY<}DOF4BFc>oH<+*sWy5S1`mn zF_U-HR381t#PQ`v5doZKTAbNU&Q!FVsUhGIj1!oSU@eSlp5BJPTk$s@L7bUstn`sLU5{#Kyg$T}jmaPaIaQUY)z>ik7Gtj+=Nj;AU=gg&6F~`6+*>>bh zaKRIBVV{_t+a0vt?L;AJae1#NN3)b4T4J^{&oTSdK$>TA&jL2srV0Bw&K~20G=K|j zcmh{_ur7h{M7$gy0P9R^qHnt{2bc55gi`-njR>CF3==d!!^0k-~D{^(9K>;EN-H(QO zcZVNtB+4?UGKW*dGw=#54>WJ8zmpFY%WPBA)rS~ zPf*sTprcOzJg7evUSu! zamXo{%o5}g-xEvC$qkF|h4Yc;6zl5`G@*CeNRuDYY_Il}tj5jasMb`Qx$ZH!@Y3k6 z+vHg^XC|{@Ma$u!yS5RwTtFrB_OZi>IH14e>hHj(Hr+h7{XhjbX zmagNjzDdLH2|so87G^T9=ht^OPok%n@-B7JZd+EBohHA~h|rvTnJWJ-cH5wU9a3e0 zvh1;5>}1vXA)efRhiI*5y=m#|(c|RZ5MCv^G^Vm~bPhcT-P#6llM1*B)Q=|}n#G%- z`-^P3y#>dghcZ-yeS&?^yJeObqdBxnZ6z*>=yfI!cY~2T5*cEWyWcUED2Q2p@DKoz z^OkzZ20>xZGW_|beg{&(M*r^H<#dy|iqOg^qS$Jzp;gQ?*iK&xyqwoSNqVV9;-wY>Bspr8Ti;34;h$o4MC1^b+y{g*55ZzjeWc6f)u8Ng9YEkK>jNC-{Gs}VJgcq(_Z-0ggT3-5t0G)sPE93~qXib;- z5LBi{NKsUJY%s)ymtC2A6uR|VkQQsmlZ8kUrOP}~K7(I=^oSkGxQw1GjA0^MV%;%L z0MBEeSY!ch`*juR$+7!jxlX!YaQFf2)qaVx6X=@~yOIY|;Q7Tu&urcxOemAGWQ(_% z&%;!GQtn8uG%}mcAx~*me%RC!O0xY2>NJ^*f>P#Kp-eBx45d;fTDndGZeXa&yJQ*0 za^P$+D(OSmdXmuwlJN$mZO$v0QWU^gG(CY-0dir%z;;(1zsS?Q1AKQj86wg$o7 ztaYCK?g)FeF_ehxGfp3bBUXIuApba`PhLixgH}sI7BA?5T!650fhsDPJussQVzT~L zP5z4y@!x}?g|=E(0Tcw}790dbGQ|XgAO(pKDn<8@0#K@EpoAuZF5va2QMp}pDk7RR zQo~vV)0?F%tU^IPdpV&b?6r{KV$U;U+A#_+^7mH^Q|6no{|gb${o(8lWT=GQf!OKn z7SHRJpQ4oz;O`yEFG^0h1{E6PX?mV5jwt~=Im%x9VoS4;QCgDzQhy8wG}fsV1JO1V zcM6lDQh@)v|NL%>uhf-KE=_w#{GDgG=1DGP^8y_P>Ioics)A5zUA;TspE3o<7$qF=&{j!*nQi@J1H*qy&fRj5}9W1>v(;&Vb7tAwk0(9 zX1sh-ItRzL-7*><-FadFS0C!q8K!i%5?|hQ67tW-8Q|}R+f@|t;Ic$CbWHI!seIY3 zIe^OgvEl}gt)2MvJ z;gtLYk>PVo4kG_^Iw>~XrqR+p-OR`089eK{vweJqASd7@vpFlX(jNH;^z~{Ws{A6+fmmO=-OL;THV; zus@QT@>O?g;0>5_oN7s6A7PvE~9pb-ae#N05e%sWJJtWYNI&ELSq4mldQ2=9# z`vU(jc>Y(av-6N3Ae1N|AOimb-s~ZM${Za5pr%El7L$$7&vy&yFYxq@%bWY6mo25l0o3OGDC2c!%j@--0`U3x+zz69A0F$wMN$02 zORhsol7=%CP5jV;jLF3iwdX9hOGcD6I_cCYPwEqhIezA^T%Q<77F`*0GiNr`~`L^B*Mo>e6ZO63)@J@Fqo>rU@%4g zBQ>m?f}iZCwpg7>R&Sj{rVPv+iupA-bbx1enWI+;``7|Oa603ZVjH;wL(-z&0Znn~ z5H9}mw0MTe1(!`*@n#Iwq7e=93k5VifES@sNo*bC9=`!3ii(saI8k~MU(3w{W)7{j zUX%$8JUix+_eX&S!K$iFTT_!=GiOa}i2>Qlq6IhOcG@ehjGEgLCyOEfv2W?$yv1pA zIb$!pW<8rs;3lQ>&p@Cd-A&~|d{)*yLI7wXBAv);-Uzk8`9NG(Ky@37L}C>qfUd6e zgMD-F76jWB3f@)Y8FvYnC7_nl=kLP-EIK8{+(i0@Bh^x9*Ey`dUcv1SFbl|8Wbv+X z+>Dkf5qZzB{ae|1+de+rvRmLoGeaFkTUW>|t2w31FZASyo~G8RV~8!DIzpA#uX0+B zXHtKPVE(#Qq>@_9kejW*=R5@qa7|1{-a~8>5rzd3_~-AbzRQ(`p<%kc!Q>RHp{|e4 z>=bO>kc~5O#H+3iU!9SYvvKvKb2bkFx_(qz&lP%RPW6rF=4zWu)Z>aAEaQj;Y>~C* zd`Ky5dZEUEtA5d*WDQDWo^GBzYRzxlwa^Miq`Dkc_xcY5)mpuSg>3PXOZ9jr@1l63yCA+^HtdWt8pJ@|jO!LFGFVy}u}e z`9~i8`sn_Hh=0)wWZv|J88rD}5%(K@m0GQ%LFkt2%%nt~pa*fxR4_oZ&z6)y*p{zV zRUn*J)hw+z%(U9$zKy`?{&d8xow>zdcD6xKtAXOU=+D5)B){w~17M;fWPpO18Wz$F zPpfrhxkK^mad29hK&^B(9#oyT-bQm*N)ngJ+l_Z0NGuDw{ zp-TM`@@k|JAodN{0HDOHmUqiSZjMZv*}sq(&f21cTnsw7^9vEr-tqJd5DV08SVD{1 zDi$GWtahLiXqnw(&tZ%5tDgmLru-2(yb4vjZ(qv5W3bNpeGw|#&y9OFCXZ9)J-kpE zU7p*%^z+d(+ha%34Ov~uopAsIdP(*$g;)#4oa*b1rnr}r77$-V?h9Y~C56Hp(qw%F zJ-9GRmRO`9g&Z|YW&CcEAca>8NAkmzX>yoQJ$j8rsV5k>5eX~uOPh3OcqOcP@HE!W znPD$aTWvp2dkyt=_;I>RMQkU?8!MSxIJ-YV*9F<(K+HWl zfgi3a;9LjJw*hu7#j*MvUvvTj?%W@Y7tDdn`!|@JbUr(@HCM^e?U%fAWYDIa&pXU9bBOn4OH)GDN@ z!C859;_}Q9pQ>Btil0}X`c44zc{qF2d0_zX_hEycusnBiKQCvX`r0HMy7gwSAF$ZS zf4Z#M1i(MwK8bchM%z_W2mBH^kcy2gXpsAiRk?@jO%5D#x#tT+1?*|L3_fb5`ZvWq zwB;P=M;{(_5>Bem&Y=Y(Z8m_}xu_*Vz#+%y9Z{{#P^mEPr}wM4p+l^Ba! z^ZK?EMLCCHGQ9UQ=|*cl&?WM3mGivfZtrv-tEkKkF~T?3@IW)kyU>5Lj(oVUsPtcx z_4F_A`2Q#Cc#iM@d1($xOUmeDf4%UwS21vCBNODsH^7<@l1M6GW+SkvvW=Msw6IpE zvu`k+_=@i1oSv56L{YwJaQt!9grhmvmP9@*uZn_1YHeMI>_XmPyjwHu}yYeQF zQ_0X$d+18Ra;isQFq1C8Dugvb=j^7A;-)T z8Kw>?m8MpJmwyhH10(K;hEnpTs$(9>q=neA*AeB=PclT})o$W0;XjvwlPGlY>qu$5 z%)3zAuD1jy#z8G)yz+!myes)LwIeKJcV+cauP-!z^ibZFRWn$Jj$HJypESxTxMs%E ze>(K3yoRkWh{Z1(r;RdLwaI*MJ@*htv`fr3Y+B?*Tk zPDkcp8W}1Y(Fcpzh&?}(5E+Ov{KJUC0zOyyw!#U|cpQBM6$~RJmDIz_zt>A?e1Af~ z|6Cl#{$l=BDx%hbDN2}Z!EU`yxISBGo=t!u;mK*g=+u*3cL+3ENWIM}%?^ecw&te5 zW_gC7GXcN&qcMoFNQF+E_xAt!FLiJ^!K!~m5C0?j|8;M>92CSQE(aatshs+g6eTnY z+j75!X?mS$FeESvi6JCto$$s|$T=AR!@b<75zp6Sfx(qnco*g)2L$0em0$*S%hbZ z`hR{Vo>@$__3*(XJr3L%zu&`(nXgo;G|8N=TXR&Gd5=~jJiw>ohjP*CYcIY4@=&rE z#Xct5tax4~5wZGoHx3C$T0J&7M{Gm8>ts5@f6=@3W}O+RDSWrtCR6kTzz-?+Jw^AQ zghRGphBr~sclWV>=aNiI7*K9ul%#XN0L_Sy$>YiW`mqe0N2Qjo%HtZJGoAims7@)$ zVV`7E#JR7X+f-JNM5O|kGMDB732L~GrrHBNKs{~ch6)pyDR{TwteT!X`9@2aHM;hy zz)X{d485vt%S>Lv)4<+}VBK;W9_yDArFAvn1fa4uq#NFBz%4(=Va{dR6{#y12G{=r zw|<4N=N`QNPIBsV%3PzXvTM0=e~VduZDwX>o`Fzcv^N#4``PH`*2NCcyi@AwT4&G9 zm|QqlDoM1640-GiR+*aX{SbyyNP-J8gwrG&2ECNMNaZ=;{(?ag;EJ`c^sO_m6WvU& z&KW{JWfJLc6TN_=I|p{1w+xMP3IYFTI>ua1UA^EfWIRHwk9uU_fq;KOET5Y30Cfb1 zk?ipC>Sui%?L`3!WtAX6cY{lOm!ucULQR)dG;3^!tTW=R%&CfK(}|8lW8zmCve^`iz7gS6@&q+I{Bt&^)2la;H9xqXTQ2Fm}r=k9Vqrd)7KLHr%9Fp6vDyI_5UvX;1dCZ4Zv>} z$ryCl=d0hZ1NyKUXwe#Ps)wBY*-M@Z=iYd)UZvQHuDZ1>wM;%h{+pgbM z)wWWm6In6A*7gjrvMBF64|94eJB^eNp6T@<>=JdtS@E8V!;aO+YJd^DfZO#Nj2wE6RN-CJ?_k8a;F8f z02oeQBD8u)&aFG<5~D*;8i7#oOmpg9UV#=Hc*jdM$QC3g*sfMlW@m?O*WxO5{6cd3 zX`ejZ3ysbJ4C^osr=4^_<}DyInJB!z@Tf3ms3<=>a}YcWQyM(IagxaqV5^+3PRm0S zETO@Ck9QOso5yG%6F3H6>UM8A{s|Z|+TQZKdP_YYw=42PI*Tz6EO+ZmT3cr0cyVA^y%#9?eYNQ2o-rbVekn1#E|tto40;x zKcvM&tt1g8<&8v4kVLh!d^QxbXF|0dDGpU)vO-C0#it~lciKZ0=teFhq38x5LHsW3 zmVFmKm-vu)H3_ccBrwtdF@;CkT(u*-lG9TC+)?U`%n}V%SHy4%WbPm557IYD&Mb8X(*P4x^A(SGZECio_ z*s4!Y947&NIu%xz8-5lJC+fEw@NF3@KZF}VwjNyT!HaQhw&u6R177I=cCNcov*|zL z4sKxdF&uJN0--#AC2sH_I?UBZ^j&k(?JP9jNu0gIORjh@^dCeLH$b;*K7N*MJdO03 zWg(1l!uXMI1#Dbp-GNQb85mVg|Kuo&%$_~6i#QO^jCanlgwna0MXz!njj2i_|HJs} z_=PkI8Q(iln)~HJ3Lw0pE`T1Vr8Mlqf1NhU=NF+#M(tAP-M(s9~Q+LW5xZ)iOJ z1(#je@5p6<(pG|a2{2uPbr}1k+3|h7!c&*6_haZcaoBWik=N?>@fi;aP7S7@xAUHE z*hn#x0M}eWpyz53`!jsehk_=6+;mtHtYVJ6*#Bs${WS;Y4k*=@q6a2jE}Ldvd@0RS zxX`!b5Q@(M9e0b9np0*xXq zOmUzs5|0}@2Q>f4|3$1sI>jOXD0tKvk4p3lRY@W&oln6`bg?^p6J>&7izET9lOlGX zab=n`!tbc^C|HpyPT>Uu^0LO)H)a$kVN8djN0gI8?-Sf1KJfI+?yp3OdW5L%Xo^b` zM-xA0ssWRA8Cb_r!LI=Mg}x9d6v2pyq`XmuCbQIADUu&UM+(y3T?u70KO-A&|4XT{ zLZAkCO1+p6VAp9;8U0(41|7~VXmgnd1BDA4Z>1L}mJ(G#e%vx-V`ztQzJc+0b<0!o zFO`x1!Z6fdkiXQ2oeVkK#3I=(r&9fodAGTn-`|gqSV3Sd4(2M&Nn#8MW1JV>rY2*e zp^1L`GEBZQfJHdqpb+Nd(mlJ4WVxXMC9@+r12TU!qw#5sgwj-wc}Q4jdCPPT{ETF?@Uj>Nt8%IAvk(o0faQv<++d z^?{2ephHKDBrzhm2lOkIhqLVJ^fhW2TD{@?xA_z1IGCgR-Mf!ATb5BBTW z<>EuEG9#_MtNM2?NFkdi`!x|invBmdf}BIi01*t0GdJHs_i+SZoI-BAG8E|ROq3vP z)j<=o%JEUO_Grn7S~%HV8Wa8z@6Wh1y7J9Q!l>En-QgU_Xmy8*^8Q#kxl~)->TA(v zef4ykvNXkEO(it9N^k|u9A#!R=ozZMO&PvT-a!#AIvk@yg9>dq<99g@HJO}R_J^FC zBn${l$A3ZpONaA}Hp2G5WVV9>0TKG2WM-Dsf=RQmWE$xFjS!((M_MX8>^?*%zX2k@Xy$a~*t`>n;%zt)IZVEq<~ z$RxOMPxD>j_Q8hmw|rme{S85It?&?zz~@bM$b^9G{?s3TV8Q=tjAaFXEeu^N=8ZyX z40~c_xY(@6`|CihpJU|>Ln1%kpy&^U(F}GKPNAjbhXuMv5@>(yYKiigyZ>OGMJ%P6 zN9rD0KLEWk!=(zRo}03Q@+Ww1$x(hyc9g7A%x$VaKU2#3UIk@}$Fg)IW%)%Wof>;q z)dV}iqeWM|E{}rB?0kv%n5nObtjBU?8ZOOJiT;=?#hpXeQ3kB91nr7!no-pXBb$a> z7i04gJV$ozM6Q2LI&Ob%<%B**Zh2eH^OS$-D*&{gUcDd7rb%0h4Ppuv|5*CM8+@|H z5~qGbwVz(ilVPn-I!lIP%bdt88T^TJug8iaNclGU|UAFJt|9q z96;UBx%57ZCC@F?B!Ie&(}=YOZsx+anhH%RudwPi=BCupCc^yN;saDfMU0y8boIs7 zpk`aQh{3}FhRt$rl*0xyw$*YLcH|(c?8af)PKtR^_J`a|oAvZ`_L{lbdYNPFr*2X%M5x^>k$K`6R_9iuS%>}$6YR!#e*x(9F^Y)fT zFJ8NQ5QCBlJJ?pKkf;nIXHUd&=BF(MGOOXAI9`0fqW_X z;!=^x<^JJaZOxT6?Q(J8R_XS*_D(i!;4!rv3WyX(?eL!^JdCE1GIXA;nG^FHq?vlj zk{WZ5s?kVJd_$`1_cg{ZiIR$V=z!DI12(eSSO-FRfl%V?SoULOtY-@HdHbTJ2|SON zSp-@bvu$}3baxB7TUSy?$P3Kk6b}utoD7@wj_IJYb6LpnoG}AYeTX|~Si6l`^agE? zPUQyM^{XM?;R!Gr(MV@dYC|j>=}a4nQ1H(1dPf-DnNK@BNBHh2obBYi34l?apkiBj zQ3xy+A}Y!pcrGQI2#}4{3KJemmHleLygC|QHAH2zN-TxjXuigz$H+A2C3G?ygw13v>_}Q)=jIGy(J;k;GZ)u$c9OXKm!Zk4L{=it zOtz-}!cADTgcd@Ua}TknHh?>i=Ah>2U!GV}D;)Qje1rwu#P2Z_|vpx0h50+0zWP@{TNcP;s0?A5KD4E$zWB(1)gq8MCVzJTr2npH)Wk9bQYzkJ0{|s zfSgN(g&S=+JF@WcLr9q_Raf|}Xg&C?AUuSv8p+*(Yw?O;hFO?VzK%Fb24G9H&7NO} zk}^N~6=L#03rmRt;CE-Jdj+sveP_3Vq$BS;uyy=h{ocMJ=^Ot%dEH;=h@gb8IW-IB*TzqHV`{AfTZAvjsWQMAAOx zrK8>Xt0X!Oi*?q+V4B^hE@UY}2NQvxD%I{*c_t6IMd3vi=ib29v~BMJnxMlYzrT@y zE!Ic%YM!YIz>0zJLuX|pr;SGF2?a2lx9c+nk@y`MiuEzQTDukma~(qgw+cq`LG8o{ zmG@7w2nz@&B6;zCAiNjq+mDAnAirig5-cQOOWYrrju?**(TNszhb!$iEKz`Z;n+LWu zM3sRu6IuFr$w7e;h6QO->}chMx_INTlVMSY5e5SOMoge~?tSG;Q&%lpRUfPI_0Zap zi`WZ*PJ%Ms-q8R3q;BeBFx79QY`MbqGQCMvEI*Oze3`^7isChyBns#+IESY?9A&sT z6y^2m)n>f92FQbl3RAk1EMViOCwMX^aul=@+Je9^I`v`2ZWlVuCYzn}(n4CvyE+on+*XzbWTn({Mq&|Lh!8xIr6BWqd4Y`+e(;ED! z8}OY%YYdEKpz)y7h4TdWYpcv~rcd%u#YpQ&4aHmW`#!ia=FXQ$k<}R8A9V=i7a-r@I|I}1Cc2k z$Hr64_0FCw9RBM@Yp*q6;_q^1fy4P z(bpznR@&%Kclg7aE87k#9EDJzM=(NYXL?PS6m%!s!P8 zt=)MxPIKMf7}{!W6SJd~s_shuy$C;q9?PW)AF(x#TrcHdIgSkro4 zahz;Q+4qLXxHZRNVdh4*uK=JD{PrYdb?~euzuzcniLv0(g_gGwGYE^SvMQq(|5*~a zM``!z@O|HDALpbIFaZACba;zWvX7U2?e%Vl;>vU2y79w%@?+mY5M-Ba+-LBhC$x5! zFcS>veT<7Aqj-Lc%i2_M#QP&@Z40Tl^UCJviNwemWb{X@_1W0?NfRtjkV@Qf z0QDZ+AlluNNsDoNPn~3VNdI7_u9L;D&6vjSB*~}X_~?M1gFOf zyGLns1g)gx_sIJxX9|0&nusXS)pfO3V_YTlcVb{ylxhIaP@laOTXBOyLN<&V z0}8fXRSSA4TB+swnqR~xi?rXWo)~KvS)?9PCHbg2E8Y(ISA5?Gg7jsK$#r$jeMn0Y zi*hLEt4TBVTVD2-7EFru>rN7p(dASs126pY#;EcVXcrBLbS{FM&(Nk|ZHJ&wKXJ57 z$(D@K%pBMVM==5Xad7u*>(NGsq&;$zuMG$V#Smi)v}DGU-YpX}))}Vm(lors^7a{& zVHRkf(o{u@;f$T2SW^m-6NbabD&K*Se8)Ub<5L~#JHuQ@V)`_IUmOoObtyuJzC1uY zH`mN`+83e`>x<(dBxj+`Zf2Z+YoYi8u_~*%k~8prXrVh``3XKSVW@?^J@^79zF=4l5r1YsRur~&`VroB>cy&XzE=IajU9avpDm28 zj?_Fcl8^d85er3&g)_fVA~K`RE_bu$?gYe=Bb7^&urdPA|y#{y*qP-Bnd!Gf@yZk>oc?|SUZ1E4fJcD>O|q7 za>m?fsDnGse3uJ6-GJS`hbSXZY5s#`Mw*4V53xznIp@qb*zj3J_g=+I`L|{AQdrWAXd}y3 zXs4q$<%((|qq6JC8WPVXH5ta?+pl4KsQVHAN)6gY$o+7}48I;a3O+6xm>PS9{0z4u z8s^ywr(LFNWFp&5?uF9bmsRuz_4(0@bP713{r52%w8v15Dkt5wKP@i(HDzT|ah~Rp z#xKnPWCRYw(Fju;{OQFsQ=QtL`3Mfo?$-ASjPO&R{ITCB`mOWi))ynZxa{?$HgoUn zrIFU1ea@i{sa&Bw8;8;@I0?Jc+&z0y>hOk>9VBK1CRdIG zzr2tP`Yw)=jVb&)7os6i>9}tF$P7SKXg2JsxuNruT+gWTYzo#rmv^2Ha$@;C-NUJA z`c@2=Hm^^`{iAn^&S`6t(}Cj-mO&i*a8)zq2N#G9Y5n#CFdwhw-*qGxZZ zNnM(8zlmYGE%88jxU7}B9R>4}Pb%bmOYjSKHY&Il~N#SFlVf}YJQ zEPU+9AOPD9{rANMT9aCS!066cpoLI24l5oWf6Sy&aJ}G;prH5R4ct54 zv;}C%13Kdhn%DLscVV*2`d8L}HwNH#CotTsmd~xeqwHd>;uu#x?lu{^uA_34rE%FR zynUIf6dY*pz}Pb`BjB_o0*+*i7sCp{#4z!^di6|YLhID}TojNXwggC0aI1~*8j1U= zu+dz3_z{LnOTRAH&r7LMCOm9*eq1SSI_Ia!k!t7D50ntNBN;s)+o2?CR{kp>@Csx1 zQ)vMxbl_TN5GTYkC1@275IK5J_VMHPfHhk%*`_tDi*I<4-lmOEZJ#7L)$B~Os(fJZ ziLf5qYiEontFR1G6a>Up8vXJ^m(XNqBQM8%yT5%yI<>5`tVdMrZ?Ma18!WMXUbM(oKC z;dZB286@@4LBTktO`7{TPx=n60%s?MqGVF3J!YkkRp5-(oFLp-Fef-GIMA1Kz-ZE+ z^2PWfK$zE)*Ad%4*4&@_g>ls{GC{UsH1VBtRsV2w*TUz5a9(c#AUM}VqcOZc{t{}Q z)l))30Q)YS{P-uKsQ!(IC{ylj@l$@CBLKqH_0*Px(ZAC%QDr+I)X|44h>=_GVQDL< z4_ZUmo>_k~$>~g*W-pu59pngseFrfKRv?X^Ros44k2M#HuFPge2y~ym1e`8@zrDZX z1+it${6rbTxf+Q4u{P`iM#ahuniH>J0GIE^&45qp9n{#r-B^*?(iTG^2_GN|*gYBPo&T~Vlmu#} z*|gG|0m(Xlf9)vPgRI#p;iaZG3%9(OdnP7<3dU73W$IDw?eD<2KgJ zgs$dS;DxRo#X3Co78@wp8O1S^s%D;SGmJHnA*{?c`?z&>9W-!U%;UfK;Q&jx83Jb3 zb3lHt80xjzvpFLl&juOp9VuGlG$B>*4XVP8auhtDuO8 zkdxIMcVp72m|D}oJ`=-EkpdQN+6j_vQy9uRIr%4Vuhim#wc9F~vFf6&qsKVtbT8G) zx$(=4bjY4EAeZb!t&n>8lVi<`|G-><8Q?Y)%$A97go3&2ZX%vZ5KUO(ivu{k5hYD8 zz1rs+;`5oLXEx5CwAg1$w>~km1qa@4`lu4rlUw7+t%=~_RqG0~uK-`%;1Ngr!x_&g z@D45*CkRQ4ie@*I(+Iil*Cz_*oXmT_874~CT5Aw@rquZ|{(`3OhTiU%FWrJ(XI|Icw^M z(FAMEe#t9+)LvXHG-_UOG=WC&Y0>+|{%_lO{hyx|`S-&Cq7>rGf7`|yyJ~nE=--Z< zIpG#)s?yZxy26{dpcEQ(ur_vj#JIS!6zJmBvlN{On~dEZ8^V8qf^W+ieP=04SVp{L zq8?=dOIhD!-@Xetc?&L*0q^L4>Q`fa2m6*Z6}RwJ85h* zww-*jZQE93+qTWdR&%;9&c)vUVLi`WbBr0WJ$0(TxqLxS^PB(X3S47h2m_CvjB zB7?Uy=zA>A7`#0RX!R2 z;o7Nr!cluI)=i!ozV4x|SQ56Da&V@1u$d0BagE$bBP#08#J&lWbU)&!rc7e3I~{2p zv>JsLOVU5L%K0_>gq*5Ae$T{uIB)?>`=$!3b6 zTBrT0a5kLQ{}wuon7oC4YIu}NA+T$WH1WB9m@J^_w9R9wH!9dFjqL{|-}QX`l~Cqh zn3l`wDa!&IM_uY*vogsvuKP^?d#mjpm=4Dc@jtCVC0q1*SB`!Yjhs9C?}@n`Bt1Fp zV*T}kFyfM_3%2|Uu2jB~*Q?mAgIp_l{N=_`YnkiB@F>4nE!Io3cK)#Tp1hpwR^E8& zT?YWh!J(*VRBJrQ#MaIz|88r^64~8Sf%j9(dW31rMA=;Cqxnz1x874+v$66THzFs? z!>mmj$Zc>4#u}6J=kL*yd?vE@kl`P%9rj6onBH0hFL0v6AGkHz0fhXAUYw?;=8zjO z^d-4w1n#wK>L)1HeTl&vRN_xr_q^N)2}U5M@`63zK0QO~5NWEMsa;7=N$n)3-j=$*Wn9dn+^T7noK(ucN@W9% z47Md5UMq809N9y}eC0a>Qbri^=ec`jhgpjp1}K*=;i2ZRh78$@XK2@j9-?26bFbfh z@asnq(O!^{o6ec_1i{t-BvJ{?!ebL+_4Fhe>?3E%7gxBrt9P`#0#IO-(?Y&j{5p?zJ- zoyysAuntO>Ym}of{o_W6edLMd73CSc8TRBgfo^1GKkPqlyF2|l6F6ky&M27V3#Ts@2vRIH*{iygOb~`f|oexMToOL4dkot;ZCLlfShXg?hY3*`P zTPqH5L{fWfRTDiz{0lCUolF#xtkXAcM2ktfHj6s;R%@uDQE#%2H2!*o^r=V~dxjJ1 z*vlm3mzr}qwm%(ZJYWoF$kB!uSiyQpxu?wIMjE1nUQT&lbxnl>89fa6JIuk?p70+P z2a>f0k(R0`6gy|9hk8(GZh+=nqjC41XK@MNgbS8@$^1~qzE!+aQSJtzD1j0Bk(-$| zIr8diKlRD6&y3?Zcm&d@o7{?N805=PMbXQz`|ck-X(-7=>iD_LI;WHRBk&Snp1-|3 z*rJ%TI6{JcYq$S+T?WWqsw-Zc81u)EL(2|Qe zE*ENq>O|eRvg$TDIrS~W6eq@WWJy@}de}C{sV=?BxxQjmts0_MjZPrh&%mFq+Db0j z*{`b?#d`s44Rzg7b12!*45f?JVHY3XgBpKIG8)Eh@9}$9YVy|DB1;jQpZ`>%?2%u` zo@dR7o}5LTW!8rFk;w@8hSLEJ#ygD5dMC(k4{A4urO9-M_Op%TXtJ zULnG0+8z1?5+54IVAqFLQOMJ0QAYYi`rYaUf=?M3=rOV;)aXQK=exsgN0BHYB&p}+ z{W(IbecGka*X=1FDGA{f(M{ERjkb^a=EqxXH_MVWM5r;8+Zxzouy3bwqYx(>0;(s* zxJ^-slyA3(pMbR%MJkp+QnW0|Cif+g#}`^&X!ib0=#DqIrx@rj#SBf|%`BpA@P5zH z8g0(csXG5dH4tJRx1cRVzR>=Rks$x(?T1hO*ZpJPMb zKvq;rmqeaa;-vxGL|5#bA5=U$i^A0>m`4xeb!P4Sbk>wj%`(~TYJTzextmh6Az11p z^E%V}*5^6L>#FS}=RViz>bL&aloKP$9L--P>Lp+fa6c6|>)}29Y%%vOpZ#(l6(e*% zb$Clo^_A#I(ZJque1c6pR9G~+y#=BW<@0c__ zx(vWc^}G8i0>8rE{m?V$93Ar1&pEpL+04$(fu&AiRyNp`3Z0YuC7o-M+uDG@mVm^Gfm67L>0tdcME^L5M z9;aNzjLZbb!1&JJd3U$HiOXnkax~9&ScvZWdV6uJvD#~8`Dt6Rt`yfg+v~x{^Os62 z0!PTCF&X>jq{=czY_Tk#sqIpsg*k@VUGtOO>g;w0E!yVx^q>%w5*yRh`sRj{s+|{A zQ)M++1AhOn*_!Ioj*hNsM4mtAaIV1b=ZELZb68hbNRi7lO~U^DBXrrn+fObRk<35Z z3UBue9b$sBZx8Jc?0+IkL=S&T@x}j0h|YFI$)Lee_5jU5^sQ?RWrBlNO2JOS3IWRNUR~Uz;ewb>#+%A(%H) z#f*>}gUf$=h7{&RH=%2%XW87=5vxQGMqNFe+LEr7UdQ0{&)o{~wW}(K53W*hPsKxj zcb%4P_K&!SJgE1n6E@F~N>M+__H-=p7-Cg!0~t6J^4_Sv-V}}@Pk`rFAW`sEbvXNh z(+Tkc7ZdOcU)DHwSx45lTiFwEy=H=(IzB_&OKONKN4y&1rk2|a>R+LS$8yQu@}F6M z=a@Nt*nwy;Ydk=!h3@6O`zq_z)RHP|gGR!OfG3?VIcCGYiLvY}3bEOW3$PX#f^V$v z;V_?w9>nDkEeJ^}JKd|BC6ua)Lmy+XE}E2_OyR4vrzcwXHJFtQlcED^Mz64=(#4re zBnG-HT5O@I4>W&2w5fYf>KjuTj^$+H?#7Pes4$85vIQ523WC{t$(+TdR!d#gX z>-!e<5Cs^`etP%!OIM=fG2glrVR4w*`Rp9I(FixK(tP5TNORc#=_E7$4h-Y=y*W+k zl9@j`^J9(L$xtRBXiR~?`VT4cVnpoEu~W2nmxA3AGe{9FXooD*^SyXgoG8In2vd zwy_A~#_d(@k~Q>d9JC<_3tCBkm?z^obvlV+87<(&>a`2mpnQR;xJgaDAsh<0%7*M@ z15=@nR?4*+%0lEmHjY@@9pMBA8-haZ0@!R1586ZB0%iGLlhM&+$)dosGFzNaE}1O- zP3_>3l$6LZnkot+XMi_+;RSYZ%-$eFSyv@MVzwElzOJ>%z1m-QoR+fGk=2dY1pRZ~ zohG-Hfs2#G78D2!gia-=W$cVA&o}p+SZY3VsW=2t^ANsucAQ1JjnRrbvPJ5|*%H%N ze1VJ>80N5iF!7Wu^g5H$R+9M{nuFud%5>W_%yByfyHjvW+^u>LdvAjS1R(xf(0}H# z{v{(^eo=nN8P3J%nz=D!d&Be5D~}~ z46>pkz{LOCYFPjB5(-TtFD{Z{yJlG|oT*Va6{vwiTo3rR;sK<~^omr5wp?OsMEhAS?(=bMc_|KrgcSOILA8 zal2i)CmrS5n){rG?08?f=u$>bE)8nzRS zR-At7_(`6UW1gH6x&I;!gFBtPfoR=zgHE7E-#}R2iNMPO<^9rraRAwDXbvg1Xq==uFW(SZ8Z|vW8mc9X6 zWX&%j|2~>q!a_GRuh~-5CidJIch{5EuLZaYx!fq2H4^_^XYBC*Vf|F^ zZ4%GMQ&K&a%6$3C_cd^A5G84?@6Gt(W`X?cPZ~B)8#o>Ovgd44&nTU%@a;sN*pdy) zo_wCs9orQ_1f_(FQv{$U_WdhA%(mpdEC$}F-JkccRQnX^tp!C1#wQD7*5)C6^X12I z?j$Y%d!TR|3i-8_@I^2`+mqTI_9T<{hlqpg zmcF+9sQnF9#W4Wy*P*vK^G@h;Amf}EYoyx3=joEhp9c^=sxLrGg`vf44HY(NG)J+| z|F?U2U_kV$f4xSVN0tuQufwaVu{g&Bm6DqFM3r%*Zb*E@1)0OknrZfV29iRO0Y;K6h1VcKwT!0*Za171EDtI+fsc@_|X>g|s zNk=>k9ZiZ0E6-{Lz%bU&j#34iXzzv_W z2D_9C?6=D=)@M#tf14cpSP_CZZ%J}Xf0&xQpY15NS`vU$89J3k;ZakLWw|a+-q1Sf zNppMF#yOe1wDEPAbLJ@w6t{^&-U#_r;o65=9~Hwp-A@0E@GGYUMy)A2`cmpuC`d$*xH`Q(~S z)I#_{A-VTwlQ$upw&Un*STJ3R3SNO8*A%K2k*2wUtpq|}{&)nn0b`9yM^+?Z1=mk+ zO0_MZYB0qslkYW?8q|d4XFKz1B7EPGyaoaeW=>7tV37Vg8P7eR5q*+wfymh&iaDd^ zN^smWa}TmP({jw(bfT=O865K){6a@r$6BUd<&vX>eueAMk(u!?Mavj8$KykMSd*Dq zfD8K~Hh(7ZG~pb<<_I*)x@IPgFAbF0CNnd; z(AwglQw8@c1&g4g+(vo)r^eALl*>f&SI|6l^EuEwmGfJSL19sOkmpcAzGQXi+8D|* z{O+Wc_>+=gvg!>I{!pu(M$`%0DGK?7GHTj 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 VwlMN}`%er%aGR6olj~j${vQ;P=LY}) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee73..f398c33c4b0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c20..65dcd68d65c 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/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# 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"' @@ -143,12 +143,16 @@ fi 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 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/gradlew.bat b/gradlew.bat index 53a6b238d41..6689b85beec 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% From 2bd2c7ea9575b3248a8ccd47f45c3e64e049774e Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 28 Nov 2022 18:50:32 +0200 Subject: [PATCH 041/140] Read shorts properly --- .../java/com/jme3/scene/plugins/gltf/GltfUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 07ab0a1f242..d0506468080 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 @@ -38,7 +38,6 @@ import com.jme3.scene.*; import com.jme3.texture.Texture; import com.jme3.util.*; - import java.io.*; import java.nio.*; import java.util.*; @@ -384,6 +383,7 @@ public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) // 5122 (SHORT) f = max(c / 32767.0, -1.0) c = round(f * 32767.0) // 5123 (UNSIGNED_SHORT) f = c / 65535.0 c = round(f * 65535.0) byte b; + int s; switch (format) { case Byte: b = stream.readByte(); @@ -392,11 +392,11 @@ public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) b = stream.readByte(); return b / 255f; case Short: - b = stream.readByte(); - return Math.max(b / 32767f, -1f); + s = stream.readShort(); + return Math.max(s / 32767f, -1f); case UnsignedShort: - b = stream.readByte(); - return b / 65535f; + s = stream.readUnsignedShort(); + return s / 65535f; default: //we have a regular float return stream.readFloat(); From d3fb13646707e3ab0a99d62b943a67db49fd0eae Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 28 Nov 2022 18:50:52 +0200 Subject: [PATCH 042/140] More efficient logging --- .../src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 3 +-- 1 file changed, 1 insertion(+), 2 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 6daad31d6f3..6a0c9d5ae47 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 @@ -1282,8 +1282,7 @@ public VertexBufferPopulator(VertexBuffer.Type bufferType) { public VertexBuffer populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { if (bufferType == null) { - logger.log(Level.WARNING, - "could not assign data to any VertexBuffer type for buffer view " + bufferViewIndex); + logger.log(Level.WARNING, "could not assign data to any VertexBuffer type for buffer view {0}", bufferViewIndex); return null; } From 556b57c540d788dca96eafaeea482b82245819c8 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 28 Nov 2022 18:52:27 +0200 Subject: [PATCH 043/140] Read unsigned byte properly --- .../src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d0506468080..a20920815e0 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 @@ -389,8 +389,8 @@ public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) b = stream.readByte(); return Math.max(b / 127f, -1f); case UnsignedByte: - b = stream.readByte(); - return b / 255f; + s = stream.readUnsignedByte(); + return s / 255f; case Short: s = stream.readShort(); return Math.max(s / 32767f, -1f); From 8fc40799dc6075128db32e6624f3c74de283a5ce Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 28 Nov 2022 19:29:06 +0200 Subject: [PATCH 044/140] Remove unnecessary byte and call the variable c as in the formulas --- .../jme3/scene/plugins/gltf/GltfUtils.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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 a20920815e0..e5b42d99a18 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 @@ -382,21 +382,20 @@ public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) // 5121 (UNSIGNED_BYTE) f = c / 255.0 c = round(f * 255.0) // 5122 (SHORT) f = max(c / 32767.0, -1.0) c = round(f * 32767.0) // 5123 (UNSIGNED_SHORT) f = c / 65535.0 c = round(f * 65535.0) - byte b; - int s; + int c; switch (format) { case Byte: - b = stream.readByte(); - return Math.max(b / 127f, -1f); + c = stream.readByte(); + return Math.max(c / 127f, -1f); case UnsignedByte: - s = stream.readUnsignedByte(); - return s / 255f; + c = stream.readUnsignedByte(); + return c / 255f; case Short: - s = stream.readShort(); - return Math.max(s / 32767f, -1f); + c = stream.readShort(); + return Math.max(c / 32767f, -1f); case UnsignedShort: - s = stream.readUnsignedShort(); - return s / 65535f; + c = stream.readUnsignedShort(); + return c / 65535f; default: //we have a regular float return stream.readFloat(); From 9e54d443f50dff600c5561f546a18031a754a580 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 8 Dec 2022 15:04:15 -0800 Subject: [PATCH 045/140] move SettingsDialog and ErrorDialog to new jme3-awt-dialogs module (#1876) * Refactory Settings/Error dialogs in JmeDialogsFactory and jme3-awt-dialogs * add build.gradle * Add copyright headers * Fix formatting and documentation Co-authored-by: riccardobl --- .../jme3/system/android/JmeAndroidSystem.java | 32 +- jme3-awt-dialogs/build.gradle | 3 + .../java/com/jme3/awt/AWTErrorDialog.java | 98 ++++++ .../java/com/jme3/awt/AWTSettingsDialog.java | 282 +++++++++++------- .../jme3/system/JmeDialogsFactoryImpl.java | 48 +++ .../java/com/jme3/app/LegacyApplication.java | 4 +- .../com/jme3/system/JmeDialogsFactory.java | 51 ++++ .../main/java/com/jme3/system/JmeSystem.java | 40 ++- .../com/jme3/system/JmeSystemDelegate.java | 81 ++++- .../jme3/system/MockJmeSystemDelegate.java | 9 - .../java/com/jme3/system/ErrorDialog.java | 67 ----- .../com/jme3/system/JmeDesktopSystem.java | 105 +------ jme3-examples/build.gradle | 1 + .../com/jme3/system/ios/JmeIosSystem.java | 19 +- .../main/java/com/jme3/app/VRApplication.java | 42 +-- settings.gradle | 1 + 16 files changed, 532 insertions(+), 351 deletions(-) create mode 100644 jme3-awt-dialogs/build.gradle create mode 100644 jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTErrorDialog.java rename jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java => jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java (82%) create mode 100644 jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java create mode 100644 jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java delete mode 100644 jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java diff --git a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java index e36319c0bc1..0d6ee82c6de 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java +++ b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java @@ -17,6 +17,8 @@ import com.jme3.system.*; import com.jme3.system.JmeContext.Type; import com.jme3.util.AndroidScreenshots; +import com.jme3.util.functional.VoidFunction; + import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -35,6 +37,18 @@ public class JmeAndroidSystem extends JmeSystemDelegate { } catch (UnsatisfiedLinkError e) { } } + + public JmeAndroidSystem(){ + setErrorMessageHandler((message) -> { + String finalMsg = message; + String finalTitle = "Error in application"; + Context context = JmeAndroidSystem.getView().getContext(); + view.getHandler().post(() -> { + AlertDialog dialog = new AlertDialog.Builder(context).setTitle(finalTitle).setMessage(finalMsg).create(); + dialog.show(); + }); + }); + } @Override public URL getPlatformAssetConfigURL() { @@ -57,26 +71,8 @@ public void writeImageFile(OutputStream outStream, String format, ByteBuffer ima bitmapImage.recycle(); } - @Override - public void showErrorDialog(String message) { - final String finalMsg = message; - final String finalTitle = "Error in application"; - final Context context = JmeAndroidSystem.getView().getContext(); - view.getHandler().post(new Runnable() { - @Override - public void run() { - AlertDialog dialog = new AlertDialog.Builder(context) - .setTitle(finalTitle).setMessage(finalMsg).create(); - dialog.show(); - } - }); - } - @Override - public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) { - return true; - } @Override public JmeContext newContext(AppSettings settings, Type contextType) { diff --git a/jme3-awt-dialogs/build.gradle b/jme3-awt-dialogs/build.gradle new file mode 100644 index 00000000000..9dd715218e7 --- /dev/null +++ b/jme3-awt-dialogs/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':jme3-core') +} diff --git a/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTErrorDialog.java b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTErrorDialog.java new file mode 100644 index 00000000000..002f80a20b6 --- /dev/null +++ b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTErrorDialog.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2022 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.awt; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +/** + * Simple dialog for displaying error messages, + * + * @author kwando + */ +public class AWTErrorDialog extends JDialog { + public static String DEFAULT_TITLE = "Error in application"; + public static int PADDING = 8; + + /** + * Create a new Dialog with a title and a message. + * + * @param message the message to display + * @param title the title to display + */ + protected AWTErrorDialog(String message, String title) { + setTitle(title); + setSize(new Dimension(600, 400)); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setLocationRelativeTo(null); + + Container container = getContentPane(); + container.setLayout(new BorderLayout()); + + JTextArea textArea = new JTextArea(); + textArea.setText(message); + textArea.setEditable(false); + textArea.setMargin(new Insets(PADDING, PADDING, PADDING, PADDING)); + add(new JScrollPane(textArea), BorderLayout.CENTER); + + final JDialog dialog = this; + JButton button = new JButton(new AbstractAction("OK"){ + @Override + public void actionPerformed(ActionEvent e) { + dialog.dispose(); + } + }); + add(button, BorderLayout.SOUTH); + } + + protected AWTErrorDialog(String message){ + this(message, DEFAULT_TITLE); + } + + /** + * Show a dialog with the provided message. + * + * @param message the message to display + */ + public static void showDialog(String message) { + AWTErrorDialog dialog = new AWTErrorDialog(message); + dialog.setVisible(true); + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java similarity index 82% rename from jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java rename to jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java index 9e20251dacd..34744b2298c 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java @@ -29,9 +29,12 @@ * 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.app; +package com.jme3.awt; +import com.jme3.asset.AssetNotFoundException; import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; + import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; @@ -46,6 +49,8 @@ import java.util.List; import java.util.ResourceBundle; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; @@ -64,24 +69,23 @@ * @author Eric Woroshow * @author Joshua Slack - reworked for proper use of GL commands. */ -public final class SettingsDialog extends JFrame { +public final class AWTSettingsDialog extends JFrame { public static interface SelectionListener { public void onSelection(int selection); } - private static final Logger logger = Logger.getLogger(SettingsDialog.class.getName()); + + private static final Logger logger = Logger.getLogger(AWTSettingsDialog.class.getName()); private static final long serialVersionUID = 1L; - public static final int NO_SELECTION = 0, - APPROVE_SELECTION = 1, - CANCEL_SELECTION = 2; - + public static final int NO_SELECTION = 0, APPROVE_SELECTION = 1, CANCEL_SELECTION = 2; + // Resource bundle for i18n. ResourceBundle resourceBundle = ResourceBundle.getBundle("com.jme3.app/SettingsDialog"); - + // the instance being configured private final AppSettings source; - + // Title Image private URL imageFile = null; // Array of supported display modes @@ -109,7 +113,70 @@ public static interface SelectionListener { private int minWidth = 0; private int minHeight = 0; - + + public static boolean showDialog(AppSettings sourceSettings) { + return showDialog(sourceSettings, true); + } + + public static boolean showDialog(AppSettings sourceSettings, boolean loadSettings) { + String iconPath = sourceSettings.getSettingsDialogImage(); + final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath); + if (iconUrl == null) { + throw new AssetNotFoundException(sourceSettings.getSettingsDialogImage()); + } + return showDialog(sourceSettings, iconUrl, loadSettings); + } + + public static boolean showDialog(AppSettings sourceSettings, String imageFile, boolean loadSettings) { + return showDialog(sourceSettings, getURL(imageFile), loadSettings); + } + + public static boolean showDialog(AppSettings sourceSettings, URL imageFile, boolean loadSettings) { + if (SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException("Cannot run from EDT"); + } + if (GraphicsEnvironment.isHeadless()) { + throw new IllegalStateException("Cannot show dialog in headless environment"); + } + + AppSettings settings = new AppSettings(false); + settings.copyFrom(sourceSettings); + + Object lock = new Object(); + AtomicBoolean done = new AtomicBoolean(); + AtomicInteger result = new AtomicInteger(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + final SelectionListener selectionListener = new SelectionListener() { + @Override + public void onSelection(int selection) { + synchronized (lock) { + done.set(true); + result.set(selection); + lock.notifyAll(); + } + } + }; + AWTSettingsDialog dialog = new AWTSettingsDialog(settings, imageFile, loadSettings); + dialog.setSelectionListener(selectionListener); + dialog.showDialog(); + } + }); + synchronized (lock) { + while (!done.get()) { + try { + lock.wait(); + } catch (InterruptedException ex) { + } + } + } + + sourceSettings.copyFrom(settings); + + return result.get() == AWTSettingsDialog.APPROVE_SELECTION; + } + /** * Instantiate a SettingsDialog for the primary display. * @@ -123,12 +190,12 @@ public static interface SelectionListener { * @throws IllegalArgumentException * if the source is null */ - public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) { + protected AWTSettingsDialog(AppSettings source, String imageFile, boolean loadSettings) { this(source, getURL(imageFile), loadSettings); } /** - * Instantiate a SettingsDialog for the primary display. + * /** Instantiate a SettingsDialog for the primary display. * * @param source * the AppSettings object (not null) @@ -140,7 +207,7 @@ public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings * @throws IllegalArgumentException * if the source is null */ - public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { + protected AWTSettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { if (source == null) { throw new IllegalArgumentException("Settings source cannot be null"); } @@ -148,32 +215,31 @@ public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { this.source = source; this.imageFile = imageFile; - //setModal(true); + // setModal(true); setAlwaysOnTop(true); setResizable(false); AppSettings registrySettings = new AppSettings(true); String appTitle; - if(source.getTitle()!=null){ + if (source.getTitle() != null) { appTitle = source.getTitle(); - }else{ - appTitle = registrySettings.getTitle(); + } else { + appTitle = registrySettings.getTitle(); } - + minWidth = source.getMinWidth(); minHeight = source.getMinHeight(); - + try { registrySettings.load(appTitle); } catch (BackingStoreException ex) { - logger.log(Level.WARNING, - "Failed to load settings", ex); + logger.log(Level.WARNING, "Failed to load settings", ex); } if (loadSettings) { source.copyFrom(registrySettings); - } else if(!registrySettings.isEmpty()) { + } else if (!registrySettings.isEmpty()) { source.mergeFrom(registrySettings); } @@ -183,17 +249,13 @@ public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { Arrays.sort(modes, new DisplayModeSorter()); DisplayMode[] merged = new DisplayMode[modes.length + windowDefaults.length]; - + int wdIndex = 0; int dmIndex = 0; int mergedIndex; - - for (mergedIndex = 0; - mergedIndex= modes.length) { merged[mergedIndex] = windowDefaults[wdIndex++]; } else if (wdIndex >= windowDefaults.length) { @@ -213,13 +275,13 @@ public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { merged[mergedIndex] = windowDefaults[wdIndex++]; } } - + if (merged.length == mergedIndex) { windowModes = merged; } else { windowModes = Arrays.copyOfRange(merged, 0, mergedIndex); } - + createUI(); } @@ -252,9 +314,6 @@ public void setMinHeight(int minHeight) { this.minHeight = minHeight; } - - - /** * setImage sets the background image of the dialog. * @@ -266,7 +325,7 @@ public void setImage(String image) { URL file = new URL("file:" + image); setImage(file); } catch (MalformedURLException e) { - logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e); + logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e); } } @@ -283,21 +342,21 @@ public void setImage(URL image) { } /** - * showDialog sets this dialog as visible, and brings it to - * the front. + * showDialog sets this dialog as visible, and brings it to the + * front. */ public void showDialog() { setLocationRelativeTo(null); - setVisible(true); + setVisible(true); toFront(); } - + /** * init creates the components to use the dialog. */ private void createUI() { GridBagConstraints gbc; - + JPanel mainPanel = new JPanel(new GridBagLayout()); addWindowListener(new WindowAdapter() { @@ -310,13 +369,13 @@ public void windowClosing(WindowEvent e) { }); if (source.getIcons() != null) { - safeSetIconImages( Arrays.asList((BufferedImage[]) source.getIcons()) ); + safeSetIconImages(Arrays.asList((BufferedImage[]) source.getIcons())); } setTitle(MessageFormat.format(resourceBundle.getString("frame.title"), source.getTitle())); - + // The buttons... - JButton ok = new JButton(resourceBundle.getString("button.ok")); + JButton ok = new JButton(resourceBundle.getString("button.ok")); JButton cancel = new JButton(resourceBundle.getString("button.cancel")); icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null); @@ -330,8 +389,7 @@ public void keyPressed(KeyEvent e) { setUserSelection(APPROVE_SELECTION); dispose(); } - } - else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { setUserSelection(CANCEL_SELECTION); dispose(); } @@ -357,10 +415,10 @@ public void actionPerformed(ActionEvent e) { }); vsyncBox = new JCheckBox(resourceBundle.getString("checkbox.vsync")); vsyncBox.setSelected(source.isVSync()); - + gammaBox = new JCheckBox(resourceBundle.getString("checkbox.gamma")); gammaBox.setSelected(source.isGammaCorrection()); - + gbc = new GridBagConstraints(); gbc.weightx = 0.5; gbc.gridx = 0; @@ -370,20 +428,19 @@ public void actionPerformed(ActionEvent e) { mainPanel.add(fullscreenBox, gbc); gbc = new GridBagConstraints(); gbc.weightx = 0.5; - // gbc.insets = new Insets(4, 16, 0, 4); + // gbc.insets = new Insets(4, 16, 0, 4); gbc.gridx = 2; - // gbc.gridwidth = 2; + // gbc.gridwidth = 2; gbc.gridy = 1; gbc.anchor = GridBagConstraints.EAST; mainPanel.add(vsyncBox, gbc); gbc = new GridBagConstraints(); gbc.weightx = 0.5; gbc.gridx = 3; - gbc.gridy = 1; + gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; mainPanel.add(gammaBox, gbc); - gbc = new GridBagConstraints(); gbc.insets = new Insets(4, 4, 4, 4); gbc.gridx = 0; @@ -432,7 +489,7 @@ public void actionPerformed(ActionEvent e) { gbc.gridy = 3; gbc.anchor = GridBagConstraints.WEST; mainPanel.add(antialiasCombo, gbc); - + // Set the button action listeners. Cancel disposes without saving, OK // saves. ok.addActionListener(new ActionListener() { @@ -442,10 +499,14 @@ public void actionPerformed(ActionEvent e) { if (verifyAndSaveCurrentSelection()) { setUserSelection(APPROVE_SELECTION); dispose(); - - // System.gc() should be called to prevent "X Error of failed request: RenderBadPicture (invalid Picture parameter)" - // on Linux when using AWT/Swing + GLFW. - // For more info see: https://github.com/LWJGL/lwjgl3/issues/149, https://hub.jmonkeyengine.org/t/experimenting-lwjgl3/37275 + + // System.gc() should be called to prevent "X Error of + // failed request: RenderBadPicture (invalid Picture + // parameter)" + // on Linux when using AWT/Swing + GLFW. + // For more info see: + // https://github.com/LWJGL/lwjgl3/issues/149, + // https://hub.jmonkeyengine.org/t/experimenting-lwjgl3/37275 System.gc(); System.gc(); } @@ -466,7 +527,7 @@ public void actionPerformed(ActionEvent e) { gbc.gridwidth = 2; gbc.gridy = 4; gbc.anchor = GridBagConstraints.EAST; - mainPanel.add(ok, gbc); + mainPanel.add(ok, gbc); gbc = new GridBagConstraints(); gbc.insets = new Insets(4, 16, 4, 4); gbc.gridx = 2; @@ -484,35 +545,42 @@ public void actionPerformed(ActionEvent e) { this.getContentPane().add(mainPanel); pack(); - + mainPanel.getRootPane().setDefaultButton(ok); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - // Fill in the combos once the window has opened so that the insets can be read. - // The assumption is made that the settings window and the display window will have the - // same insets as that is used to resize the "full screen windowed" mode appropriately. + // Fill in the combos once the window has opened so that the + // insets can be read. + // The assumption is made that the settings window and the + // display window will have the + // same insets as that is used to resize the "full screen + // windowed" mode appropriately. updateResolutionChoices(); if (source.getWidth() != 0 && source.getHeight() != 0) { - displayResCombo.setSelectedItem(source.getWidth() + " x " - + source.getHeight()); + displayResCombo.setSelectedItem(source.getWidth() + " x " + source.getHeight()); } else { - displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); + displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1); } updateAntialiasChoices(); colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp"); } - }); - + }); + } - /* Access JDialog.setIconImages by reflection in case we're running on JRE < 1.6 */ + /* + * Access JDialog.setIconImages by reflection in case we're running on JRE < + * 1.6 + */ private void safeSetIconImages(List icons) { try { - // Due to Java bug 6445278, we try to set icon on our shared owner frame first. - // Otherwise, our alt-tab icon will be the Java default under Windows. + // Due to Java bug 6445278, we try to set icon on our shared owner + // frame first. + // Otherwise, our alt-tab icon will be the Java default under + // Windows. Window owner = getOwner(); if (owner != null) { Method setIconImages = owner.getClass().getMethod("setIconImages", List.class); @@ -591,7 +659,7 @@ private boolean verifyAndSaveCurrentSelection() { } if (valid) { - //use the AppSettings class to save it to backing store + // use the AppSettings class to save it to backing store source.setWidth(width); source.setHeight(height); source.setBitsPerPixel(depth); @@ -599,7 +667,7 @@ private boolean verifyAndSaveCurrentSelection() { source.setFullscreen(fullscreen); source.setVSync(vsync); source.setGammaCorrection(gamma); - //source.setRenderer(renderer); + // source.setRenderer(renderer); source.setSamples(multisample); String appTitle = source.getTitle(); @@ -607,13 +675,10 @@ private boolean verifyAndSaveCurrentSelection() { try { source.save(appTitle); } catch (BackingStoreException ex) { - logger.log(Level.WARNING, - "Failed to save setting changes", ex); + logger.log(Level.WARNING, "Failed to save setting changes", ex); } } else { - showError( - this, - resourceBundle.getString("error.unsupportedmode")); + showError(this, resourceBundle.getString("error.unsupportedmode")); } return valid; @@ -623,7 +688,7 @@ private boolean verifyAndSaveCurrentSelection() { * setUpChooser retrieves all available display modes and * places them in a JComboBox. The resolution specified by * AppSettings is used as the default value. - * + * * @return the combo box of display modes. */ private JComboBox setUpResolutionChooser() { @@ -668,7 +733,7 @@ private void updateDisplayChoices() { displayFreqCombo.setModel(new DefaultComboBoxModel<>(freqs)); // Try to reset freq displayFreqCombo.setSelectedItem(displayFreq); - + if (!displayFreqCombo.getSelectedItem().equals(displayFreq)) { // Cannot find saved frequency in available frequencies. // Choose the closest one to 60 Hz. @@ -684,21 +749,17 @@ private void updateDisplayChoices() { */ private void updateResolutionChoices() { if (!fullscreenBox.isSelected()) { - displayResCombo.setModel(new DefaultComboBoxModel<>( - getWindowedResolutions(windowModes))); + displayResCombo.setModel(new DefaultComboBoxModel<>(getWindowedResolutions(windowModes))); if (displayResCombo.getItemCount() > 0) { - displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); + displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1); } - colorDepthCombo.setModel(new DefaultComboBoxModel<>(new String[]{ - "24 bpp", "16 bpp"})); - displayFreqCombo.setModel(new DefaultComboBoxModel<>( - new String[]{resourceBundle.getString("refresh.na")})); + colorDepthCombo.setModel(new DefaultComboBoxModel<>(new String[] { "24 bpp", "16 bpp" })); + displayFreqCombo.setModel(new DefaultComboBoxModel<>(new String[] { resourceBundle.getString("refresh.na") })); displayFreqCombo.setEnabled(false); } else { - displayResCombo.setModel(new DefaultComboBoxModel<>( - getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE))); + displayResCombo.setModel(new DefaultComboBoxModel<>(getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE))); if (displayResCombo.getItemCount() > 0) { - displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); + displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1); } displayFreqCombo.setEnabled(true); updateDisplayChoices(); @@ -708,9 +769,9 @@ private void updateResolutionChoices() { private void updateAntialiasChoices() { // maybe in the future will add support for determining this info // through PBuffer - String[] choices = new String[]{resourceBundle.getString("antialias.disabled"), "2x", "4x", "6x", "8x", "16x"}; + String[] choices = new String[] { resourceBundle.getString("antialias.disabled"), "2x", "4x", "6x", "8x", "16x" }; antialiasCombo.setModel(new DefaultComboBoxModel<>(choices)); - antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples()/2,5)]); + antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples() / 2, 5)]); } // @@ -732,19 +793,19 @@ private static URL getURL(String file) { } private static void showError(java.awt.Component parent, String message) { - JOptionPane.showMessageDialog(parent, message, "Error", - JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(parent, message, "Error", JOptionPane.ERROR_MESSAGE); } /** - * Returns every unique resolution from an array of DisplayModes - * where the resolution is greater than the configured minimums. + * Returns every unique resolution from an array of + * DisplayModes where the resolution is greater than the + * configured minimums. */ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthLimit) { Insets insets = getInsets(); heightLimit -= insets.top + insets.bottom; widthLimit -= insets.left + insets.right; - + Set resolutions = new LinkedHashSet<>(modes.length); for (DisplayMode mode : modes) { int height = mode.getHeight(); @@ -756,7 +817,7 @@ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthL if (width >= widthLimit) { width = widthLimit; } - + String res = width + " x " + height; resolutions.add(res); } @@ -764,16 +825,17 @@ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthL return resolutions.toArray(new String[0]); } - + /** - * Returns every unique resolution from an array of DisplayModes - * where the resolution is greater than the configured minimums and the height - * is less than the current screen resolution. + * Returns every unique resolution from an array of + * DisplayModes where the resolution is greater than the + * configured minimums and the height is less than the current screen + * resolution. */ private String[] getWindowedResolutions(DisplayMode[] modes) { int maxHeight = 0; int maxWidth = 0; - + for (DisplayMode mode : modes) { if (maxHeight < mode.getHeight()) { maxHeight = mode.getHeight(); @@ -813,7 +875,8 @@ private static String[] getDepths(String resolution, DisplayMode[] modes) { } if (depths.isEmpty()) { - // add some default depths, possible system is multi-depth supporting + // add some default depths, possible system is multi-depth + // supporting depths.add("24 bpp"); } @@ -823,8 +886,7 @@ private static String[] getDepths(String resolution, DisplayMode[] modes) { /** * Returns every possible refresh rate for the given resolution. */ - private static String[] getFrequencies(String resolution, - DisplayMode[] modes) { + private static String[] getFrequencies(String resolution, DisplayMode[] modes) { List freqs = new ArrayList<>(4); for (DisplayMode mode : modes) { String res = mode.getWidth() + " x " + mode.getHeight(); @@ -841,13 +903,13 @@ private static String[] getFrequencies(String resolution, return freqs.toArray(new String[0]); } - + /** * Chooses the closest frequency to 60 Hz. * * @param resolution * @param modes - * @return + * @return */ private static String getBestFrequency(String resolution, DisplayMode[] modes) { int closest = Integer.MAX_VALUE; @@ -861,7 +923,7 @@ private static String getBestFrequency(String resolution, DisplayMode[] modes) { } } } - + if (closest != Integer.MAX_VALUE) { return closest + " Hz"; } else { @@ -870,8 +932,8 @@ private static String getBestFrequency(String resolution, DisplayMode[] modes) { } /** - * Utility class for sorting DisplayModes. Sorts by - * resolution, then bit depth, and then finally refresh rate. + * Utility class for sorting DisplayModes. Sorts by resolution, + * then bit depth, and then finally refresh rate. */ private class DisplayModeSorter implements Comparator { diff --git a/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java b/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java new file mode 100644 index 00000000000..7c5b12c97dc --- /dev/null +++ b/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system; + +import com.jme3.awt.AWTErrorDialog; +import com.jme3.awt.AWTSettingsDialog; + +public class JmeDialogsFactoryImpl implements JmeDialogsFactory { + + public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry){ + return AWTSettingsDialog.showDialog(settings,loadFromRegistry); + } + + public void showErrorDialog(String message){ + AWTErrorDialog.showDialog(message); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java index 6849a35edde..626dec4bfd0 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -667,10 +667,10 @@ public void handleError(String errMsg, Throwable t) { // Display error message on screen if not in headless mode if (context.getType() != JmeContext.Type.Headless) { if (t != null) { - JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() + JmeSystem.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName() + (t.getMessage() != null ? ": " + t.getMessage() : "")); } else { - JmeSystem.showErrorDialog(errMsg); + JmeSystem.handleErrorMessage(errMsg); } } diff --git a/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java b/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java new file mode 100644 index 00000000000..63113c729d9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +public interface JmeDialogsFactory { + /** + * Set a function to handler app settings. + * The default implementation shows a settings dialog if available. + * @param handler handler function that accepts as argument an instance of AppSettings + * to transform and a boolean with the value of true if the settings are expected to be loaded from + * the user registry. The handler function returns false if the configuration is interrupted (eg.the the dialog was closed) + * or true otherwise. + */ + public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry); + + /** + * Set function to handle errors. + * The default implementation show a dialog if available. + * @param handler Consumer to which the error is passed as String + */ + public void showErrorDialog(String message); +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java index 561926a9e75..4a832916c83 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.audio.AudioRenderer; import com.jme3.input.SoftTextDialogInput; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -41,6 +42,8 @@ import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.ByteBuffer; +import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -154,10 +157,7 @@ public static AssetManager newAssetManager() { return systemDelegate.newAssetManager(); } - public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) { - checkDelegate(); - return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry); - } + /** * Determine which Platform (operating system and architecture) the @@ -190,14 +190,44 @@ public static URL getPlatformAssetConfigURL() { * feels is appropriate. If this is a headless or an offscreen surface * context, this method should do nothing. * + * @deprecated Use JmeSystem.handleErrorMessage(String) instead * @param message The error message to display. May contain new line * characters. */ + @Deprecated public static void showErrorDialog(String message){ + handleErrorMessage(message); + } + + public static void handleErrorMessage(String message){ + checkDelegate(); + systemDelegate.handleErrorMessage(message); + } + + public static void setErrorMessageHandler(Consumer handler){ + checkDelegate(); + systemDelegate.setErrorMessageHandler(handler); + } + + + public static void handleSettings(AppSettings sourceSettings, boolean loadFromRegistry){ + checkDelegate(); + systemDelegate.handleSettings(sourceSettings, loadFromRegistry); + } + + public static void setSettingsHandler(BiFunction handler){ + checkDelegate(); + systemDelegate.setSettingsHandler(handler); + } + + + @Deprecated + public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) { checkDelegate(); - systemDelegate.showErrorDialog(message); + return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry); } + public static void initialize(AppSettings settings) { checkDelegate(); systemDelegate.initialize(settings); diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index 5e701f3a0f6..35143fca4a6 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -35,14 +35,18 @@ import com.jme3.asset.DesktopAssetManager; import com.jme3.audio.AudioRenderer; import com.jme3.input.SoftTextDialogInput; + import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.ByteBuffer; import java.util.EnumMap; import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -58,6 +62,32 @@ public abstract class JmeSystemDelegate { protected Map storageFolders = new EnumMap<>(JmeSystem.StorageFolderType.class); protected SoftTextDialogInput softTextDialogInput = null; + protected Consumer errorMessageHandler = (message) -> { + JmeDialogsFactory dialogFactory = null; + try { + dialogFactory = (JmeDialogsFactory)Class.forName("com.jme3.system.JmeDialogsFactoryImpl").getConstructor().newInstance(); + } catch(ClassNotFoundException e){ + logger.warning("JmeDialogsFactory implementation not found."); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + } + if(dialogFactory != null) dialogFactory.showErrorDialog(message); + else System.err.println(message); + }; + + protected BiFunction settingsHandler = (settings,loadFromRegistry) -> { + JmeDialogsFactory dialogFactory = null; + try { + dialogFactory = (JmeDialogsFactory)Class.forName("com.jme3.system.JmeDialogsFactoryImpl").getConstructor().newInstance(); + } catch(ClassNotFoundException e){ + logger.warning("JmeDialogsFactory implementation not found."); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + } + if(dialogFactory != null) return dialogFactory.showSettingsDialog(settings, loadFromRegistry); + return true; + }; + public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { File storageFolder = null; @@ -133,9 +163,56 @@ public final AssetManager newAssetManager() { public abstract void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException; - public abstract void showErrorDialog(String message); + /** + * Set function to handle errors. + * The default implementation show a dialog if available. + * @param handler Consumer to which the error is passed as String + */ + public void setErrorMessageHandler(Consumer handler){ + errorMessageHandler = handler; + } + + /** + * Internal use only: submit an error to the error message handler + */ + public void handleErrorMessage(String message){ + if(errorMessageHandler != null) errorMessageHandler.accept(message); + } + + /** + * Set a function to handler app settings. + * The default implementation shows a settings dialog if available. + * @param handler handler function that accepts as argument an instance of AppSettings + * to transform and a boolean with the value of true if the settings are expected to be loaded from + * the user registry. The handler function returns false if the configuration is interrupted (eg.the the dialog was closed) + * or true otherwise. + */ + public void setSettingsHandler(BiFunction handler){ + settingsHandler = handler; + } + + /** + * Internal use only: summon the settings handler + */ + public boolean handleSettings(AppSettings settings, boolean loadFromRegistry){ + if(settingsHandler != null) return settingsHandler.apply(settings,loadFromRegistry); + return true; + } + + /** + * @deprecated Use JmeSystemDelegate.handleErrorMessage(String) instead + * @param message + */ + @Deprecated + public void showErrorDialog(String message){ + handleErrorMessage(message); + } + + @Deprecated + public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry){ + return handleSettings(settings, loadFromRegistry); + } - public abstract boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry); private boolean is64Bit(String arch) { if (arch.equals("x86")) { diff --git a/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java b/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java index b9beac7f69f..ca981877eb5 100644 --- a/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java +++ b/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java @@ -43,15 +43,6 @@ public class MockJmeSystemDelegate extends JmeSystemDelegate { public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { } - @Override - public void showErrorDialog(String message) { - } - - @Override - public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) { - return false; - } - @Override public URL getPlatformAssetConfigURL() { return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/General.cfg"); diff --git a/jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java b/jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java deleted file mode 100644 index 26f85be4e09..00000000000 --- a/jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.jme3.system; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import javax.swing.AbstractAction; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; - -/** - * Simple dialog for displaying error messages, - * - * @author kwando - */ -public class ErrorDialog extends JDialog { - public static String DEFAULT_TITLE = "Error in application"; - public static int PADDING = 8; - - /** - * Create a new Dialog with a title and a message. - * - * @param message the message to display - * @param title the title to display - */ - public ErrorDialog(String message, String title) { - setTitle(title); - setSize(new Dimension(600, 400)); - setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - setLocationRelativeTo(null); - - Container container = getContentPane(); - container.setLayout(new BorderLayout()); - - JTextArea textArea = new JTextArea(); - textArea.setText(message); - textArea.setEditable(false); - textArea.setMargin(new Insets(PADDING, PADDING, PADDING, PADDING)); - add(new JScrollPane(textArea), BorderLayout.CENTER); - - final JDialog dialog = this; - JButton button = new JButton(new AbstractAction("OK"){ - @Override - public void actionPerformed(ActionEvent e) { - dialog.dispose(); - } - }); - add(button, BorderLayout.SOUTH); - } - - public ErrorDialog(String message){ - this(message, DEFAULT_TITLE); - } - - /** - * Show a dialog with the provided message. - * - * @param message the message to display - */ - public static void showDialog(String message) { - ErrorDialog dialog = new ErrorDialog(message); - dialog.setVisible(true); - } -} diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 215a301e738..3a2882a241d 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -31,9 +31,6 @@ */ package com.jme3.system; -import com.jme3.app.SettingsDialog; -import com.jme3.app.SettingsDialog.SelectionListener; -import com.jme3.asset.AssetNotFoundException; import com.jme3.audio.AudioRenderer; import com.jme3.audio.openal.AL; import com.jme3.audio.openal.ALAudioRenderer; @@ -42,13 +39,16 @@ import com.jme3.system.JmeContext.Type; import com.jme3.texture.Image; import com.jme3.texture.image.ColorSpace; -import com.jme3.util.Screenshots; import jme3tools.converters.ImageToAwt; -import java.awt.EventQueue; -import java.awt.Graphics2D; -import java.awt.GraphicsEnvironment; -import java.awt.RenderingHints; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; @@ -57,17 +57,7 @@ import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.plugins.jpeg.JPEGImageWriteParam; -import javax.imageio.stream.ImageOutputStream; -import javax.imageio.stream.MemoryCacheImageOutputStream; -import javax.swing.SwingUtilities; /** * @@ -75,6 +65,9 @@ */ public class JmeDesktopSystem extends JmeSystemDelegate { + public JmeDesktopSystem() { + } + @Override public URL getPlatformAssetConfigURL() { return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"); @@ -132,82 +125,6 @@ public void writeImageFile(OutputStream outStream, String format, ByteBuffer ima } } - @Override - public void showErrorDialog(String message) { - if (!GraphicsEnvironment.isHeadless()) { - final String msg = message; - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - ErrorDialog.showDialog(msg); - } - }); - } else { - System.err.println("[JME ERROR] " + message); - } - } - - @Override - public boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) { - if (SwingUtilities.isEventDispatchThread()) { - throw new IllegalStateException("Cannot run from EDT"); - } - if (GraphicsEnvironment.isHeadless()) { - throw new IllegalStateException("Cannot show dialog in headless environment"); - } - - final AppSettings settings = new AppSettings(false); - settings.copyFrom(sourceSettings); - String iconPath = sourceSettings.getSettingsDialogImage(); - if(iconPath == null){ - iconPath = ""; - } - final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath); - if (iconUrl == null) { - throw new AssetNotFoundException(sourceSettings.getSettingsDialogImage()); - } - - final AtomicBoolean done = new AtomicBoolean(); - final AtomicInteger result = new AtomicInteger(); - final Object lock = new Object(); - - final SelectionListener selectionListener = new SelectionListener() { - - @Override - public void onSelection(int selection) { - synchronized (lock) { - done.set(true); - result.set(selection); - lock.notifyAll(); - } - } - }; - SwingUtilities.invokeLater(new Runnable() { - - @Override - public void run() { - synchronized (lock) { - SettingsDialog dialog = new SettingsDialog(settings, iconUrl, loadFromRegistry); - dialog.setSelectionListener(selectionListener); - dialog.showDialog(); - } - } - }); - - synchronized (lock) { - while (!done.get()) { - try { - lock.wait(); - } catch (InterruptedException ex) { - } - } - } - - sourceSettings.copyFrom(settings); - - return result.get() == SettingsDialog.APPROVE_SELECTION; - } - @SuppressWarnings("unchecked") private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) { try { diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index a6e9888edfc..fbed866b7df 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation project(':jme3-niftygui') implementation project(':jme3-plugins') implementation project(':jme3-terrain') + implementation project(':jme3-awt-dialogs') runtimeOnly project(':jme3-testdata') } diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java index 820e387e54b..77f41c1f821 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java @@ -35,6 +35,7 @@ import com.jme3.system.JmeContext; import com.jme3.system.JmeSystemDelegate; import com.jme3.system.NullContext; +import com.jme3.util.functional.VoidFunction; import com.jme3.audio.AudioRenderer; import com.jme3.audio.ios.IosAL; import com.jme3.audio.ios.IosALC; @@ -54,6 +55,13 @@ */ public class JmeIosSystem extends JmeSystemDelegate { + public JmeIosSystem() { + setErrorMessageHandler((message) -> { + showDialog(message); + System.err.println("JME APPLICATION ERROR:" + message); + }); + } + @Override public URL getPlatformAssetConfigURL() { return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/IOS.cfg"); @@ -64,18 +72,11 @@ public void writeImageFile(OutputStream outStream, String format, ByteBuffer ima throw new UnsupportedOperationException("Not supported yet."); } - @Override - public void showErrorDialog(String message) { - showDialog(message); - System.err.println("JME APPLICATION ERROR:" + message); - } + private native void showDialog(String message); - @Override - public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) { - return true; - } + @Override public JmeContext newContext(AppSettings settings, JmeContext.Type contextType) { diff --git a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java b/jme3-vr/src/main/java/com/jme3/app/VRApplication.java index 5d9be70c3f9..02ab733230d 100644 --- a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java +++ b/jme3-vr/src/main/java/com/jme3/app/VRApplication.java @@ -465,10 +465,10 @@ public void handleError(String errMsg, Throwable t){ // Display error message on screen if not in headless mode if (context.getType() != JmeContext.Type.Headless) { if (t != null) { - JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() + + JmeSystem.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName() + (t.getMessage() != null ? ": " + t.getMessage() : "")); } else { - JmeSystem.showErrorDialog(errMsg); + JmeSystem.handleErrorMessage(errMsg); } } @@ -706,40 +706,12 @@ public void start() { logger.config("VR mode disabled."); // not in VR, show settings dialog - if( Platform.get() != Platform.MACOSX ) { - if (!JmeSystem.showSettingsDialog(settings, loadSettings)) { - logger.config("Starting application [SUCCESS]"); - return; - } - } else { - // GLFW workaround on macs - settings.setFrequency(defDev.getDisplayMode().getRefreshRate()); - settings.setDepthBits(24); - settings.setVSync(true); - // try and read resolution from file in local dir - File resFile = new File("resolution.txt"); - if( resFile.exists() ) { - try { - BufferedReader br = new BufferedReader(new FileReader(resFile)); - settings.setWidth(Integer.parseInt(br.readLine())); - settings.setHeight(Integer.parseInt(br.readLine())); - try { - settings.setFullscreen(br.readLine().toLowerCase(Locale.ENGLISH).contains("full")); - } catch(Exception e) { - settings.setFullscreen(false); - } - br.close(); - } catch(Exception e) { - settings.setWidth(1280); - settings.setHeight(720); - } - } else { - settings.setWidth(1280); - settings.setHeight(720); - settings.setFullscreen(false); - } - settings.setResizable(false); + if (!JmeSystem.showSettingsDialog(settings, loadSettings)) { + logger.config("Starting application [SUCCESS]"); + return; } + + settings.setSwapBuffers(true); } else { logger.config("VR mode enabled."); diff --git a/settings.gradle b/settings.gradle index 78341dfa85b..3f6acceb8e4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ include 'jme3-testdata' // Example projects include 'jme3-examples' +include 'jme3-awt-dialogs' if(buildAndroidExamples == "true"){ include 'jme3-android-examples' From 80e55d4139d2c8a3d7ab56aa2ae3cc1cc8f83271 Mon Sep 17 00:00:00 2001 From: pavl_g <60224159+Scrappers-glitch@users.noreply.github.com> Date: Fri, 9 Dec 2022 21:29:28 +0200 Subject: [PATCH 046/140] JmeSurfaceView: Package migration (#1819) * JmeSurfaceView: migration to new package (com.jme3.view.surfaceview) * JmeSurfaceView: migration to new package (com.jme3.view.surfaceview) --- .../main/java/com/jme3/view/package-info.java | 36 +++++++++++++++ .../surfaceview}/JmeSurfaceView.java | 8 ++-- .../surfaceview}/OnExceptionThrown.java | 4 +- .../surfaceview}/OnLayoutDrawn.java | 4 +- .../surfaceview}/OnRendererCompleted.java | 4 +- .../surfaceview}/OnRendererStarted.java | 4 +- .../jme3/view/surfaceview/package-info.java | 44 +++++++++++++++++++ 7 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 jme3-android/src/main/java/com/jme3/view/package-info.java rename jme3-android/src/main/java/com/jme3/{app/jmeSurfaceView => view/surfaceview}/JmeSurfaceView.java (99%) rename jme3-android/src/main/java/com/jme3/{app/jmeSurfaceView => view/surfaceview}/OnExceptionThrown.java (96%) rename jme3-android/src/main/java/com/jme3/{app/jmeSurfaceView => view/surfaceview}/OnLayoutDrawn.java (96%) rename jme3-android/src/main/java/com/jme3/{app/jmeSurfaceView => view/surfaceview}/OnRendererCompleted.java (96%) rename jme3-android/src/main/java/com/jme3/{app/jmeSurfaceView => view/surfaceview}/OnRendererStarted.java (96%) create mode 100644 jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java diff --git a/jme3-android/src/main/java/com/jme3/view/package-info.java b/jme3-android/src/main/java/com/jme3/view/package-info.java new file mode 100644 index 00000000000..5b820343d21 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2022 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. + */ +/** + * Provides classes that expose custom android-native ui that can handle screen layout and interactions with the user + * for a jMonkeyEngine game. + */ +package com.jme3.view; \ No newline at end of file diff --git a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/JmeSurfaceView.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java similarity index 99% rename from jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/JmeSurfaceView.java rename to jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java index c897ab35438..d364ab09239 100644 --- a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/JmeSurfaceView.java +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ * 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.app.jmeSurfaceView; +package com.jme3.view.surfaceview; import android.app.Activity; import android.app.ActivityManager; @@ -238,7 +238,7 @@ public void startRenderer(int delayMillis) { } } - private void removeGlSurfaceView() { + private void removeGLSurfaceView() { ((Activity) getContext()).runOnUiThread(() -> { if (glSurfaceView != null) { JmeSurfaceView.this.removeView(glSurfaceView); @@ -408,7 +408,7 @@ public void destroy() { if (legacyApplication == null) { return; } - removeGlSurfaceView(); + removeGLSurfaceView(); legacyApplication.destroy(); /*help the Dalvik Garbage collector to destruct the pointers, by making them nullptr*/ /*context instances*/ diff --git a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnExceptionThrown.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java similarity index 96% rename from jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnExceptionThrown.java rename to jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java index 7921e232bfb..a0174b074a6 100644 --- a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnExceptionThrown.java +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ * 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.app.jmeSurfaceView; +package com.jme3.view.surfaceview; /** * An interface designed to listen for exceptions and fire an event when an exception is thrown. diff --git a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnLayoutDrawn.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java similarity index 96% rename from jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnLayoutDrawn.java rename to jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java index de934512270..14a7d9ca587 100644 --- a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnLayoutDrawn.java +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ * 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.app.jmeSurfaceView; +package com.jme3.view.surfaceview; import android.view.View; import com.jme3.app.LegacyApplication; diff --git a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnRendererCompleted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java similarity index 96% rename from jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnRendererCompleted.java rename to jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java index 5e1013ae663..284b8e97e5a 100644 --- a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnRendererCompleted.java +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ * 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.app.jmeSurfaceView; +package com.jme3.view.surfaceview; import com.jme3.app.LegacyApplication; import com.jme3.system.AppSettings; diff --git a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnRendererStarted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java similarity index 96% rename from jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnRendererStarted.java rename to jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java index 89f6bf88c4e..d92fd19479f 100644 --- a/jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/OnRendererStarted.java +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ * 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.app.jmeSurfaceView; +package com.jme3.view.surfaceview; import android.view.View; import com.jme3.app.LegacyApplication; diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java new file mode 100644 index 00000000000..38d15448bfb --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/package-info.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2022 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. + */ + +/** + * Holds {@link com.jme3.view.surfaceview.JmeSurfaceView} with some lifecycle interfaces. + *

+ * This package provides the following : + *

+ * {@link com.jme3.view.surfaceview.JmeSurfaceView} : An OpenGL android view wrapper for rendering, updating and destroying a jMonkeyEngine game. + * {@link com.jme3.view.surfaceview.OnRendererStarted} : Provides a method to be invoked when a jMonkeyEngine application starts. + * {@link com.jme3.view.surfaceview.OnLayoutDrawn} : Provides a method to be invoked when the GLSurfaceView draws the content, before OnRendererCompleted. + * {@link com.jme3.view.surfaceview.OnRendererCompleted} : Provides a method to be invoked on the first update of the game. + * {@link com.jme3.view.surfaceview.OnExceptionThrown} : Provides a method to be invoked when an exception is thrown. + */ +package com.jme3.view.surfaceview; \ No newline at end of file From 076f2490a502427d069fa3c628bca99b5c1e8ff9 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 13 Dec 2022 09:40:53 -0800 Subject: [PATCH 047/140] upgrade the groovy-test library to v3.0.13 --- common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.gradle b/common.gradle index da1f3461b16..6d38c283c31 100644 --- a/common.gradle +++ b/common.gradle @@ -37,7 +37,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.10' + testImplementation 'org.codehaus.groovy:groovy-test:3.0.13' } From 9d5eeee8923c27495c948be7e7c91276e739da51 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 13 Dec 2022 10:31:59 -0800 Subject: [PATCH 048/140] jme3-core: correct/clarify javadoc --- .../anim/tween/action/BlendableAction.java | 2 +- .../java/com/jme3/material/RenderState.java | 70 ++++++----- .../src/main/java/com/jme3/math/Matrix3f.java | 21 ++-- .../src/main/java/com/jme3/math/Matrix4f.java | 109 +++++++++--------- .../main/java/com/jme3/math/Quaternion.java | 32 ++--- .../main/java/com/jme3/math/Transform.java | 26 +++-- .../src/main/java/com/jme3/math/Triangle.java | 76 ++++++------ .../src/main/java/com/jme3/math/Vector3f.java | 77 +++++++------ .../com/jme3/system/JmeDialogsFactory.java | 19 +-- 9 files changed, 235 insertions(+), 197 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java index a32495d672e..ee9ed6470b6 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java @@ -114,7 +114,7 @@ protected float getTransitionWeight() { } /** - * @param maxTransitionWeight The max transition weight. Must be >= 0 and <=1 (default=1) + * @param maxTransitionWeight The max transition weight. Must be >0 and <1 (default=1) */ public void setMaxTransitionWeight(double maxTransitionWeight) { assert maxTransitionWeight >= 0 && maxTransitionWeight <= 1; diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java index 9475307a59d..8ab30431847 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -311,7 +311,6 @@ public enum BlendMode { * Result.rgb = Source Alpha * Source Color + * (1 - Source Alpha) * Dest Color -> (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) * Result.a = 1 * Source Alpha + 1 * Dest Alpha -> (GL_ONE, GL_ONE) - * */ AlphaSumA, /** @@ -600,9 +599,12 @@ public RenderState clone() { } /** - * returns true if the given renderState is equal to this one - * @param o the renderState to compare to - * @return true if the renderStates are equal + * Tests for equivalence with the argument. If {@code o} is null, false is + * returned. Either way, the current instance is unaffected. + * + * @param o the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code o} are equivalent, + * otherwise false */ @Override public boolean equals(Object o) { @@ -901,7 +903,7 @@ public void setWireframe(boolean wireframe) { * @see http://www.opengl.org/resources/faq/technical/polygonoffset.htm * @param factor scales the maximum Z slope, with respect to X or Y of the polygon * @param units scales the minimum resolvable depth buffer value - **/ + */ public void setPolyOffset(float factor, float units) { applyPolyOffset = true; if (factor == 0 && units == 0) { @@ -966,6 +968,7 @@ public void setStencil(boolean enabled, /** * Set the depth comparison function to the given TestFunction * default is LessOrEqual (GL_LEQUAL) + * * @see TestFunction * @see RenderState#setDepthTest(boolean) * @param depthFunc the depth comparison function @@ -980,6 +983,7 @@ public void setDepthFunc(TestFunction depthFunc) { * Sets the mesh line width. * Use this in conjunction with {@link #setWireframe(boolean)} or with a mesh in * {@link com.jme3.scene.Mesh.Mode#Lines} mode. + * * @param lineWidth the line width. */ public void setLineWidth(float lineWidth) { @@ -1153,71 +1157,81 @@ public TestFunction getBackStencilFunction() { } /** - * Sets the front stencil mask - * @param frontStencilMask + * Sets the front stencil mask. + * + * @param frontStencilMask the desired bitmask (default=0x7fffffff) */ public void setFrontStencilMask(int frontStencilMask) { this.frontStencilMask = frontStencilMask; } /** - * Sets the back stencil mask - * @param backStencilMask + * Sets the back stencil mask. + * + * @param backStencilMask the desired bitmask (default=0x7fffffff) */ public void setBackStencilMask(int backStencilMask) { this.backStencilMask = backStencilMask; } /** - * Sets the front stencil reference - * @param frontStencilReference + * Sets the front stencil reference. + * + * @param frontStencilReference the desired reference (default=0x0) */ public void setFrontStencilReference(int frontStencilReference) { this.frontStencilReference = frontStencilReference; } /** - * Sets the back stencil reference - * @param backStencilReference + * Sets the back stencil reference. + * + * @param backStencilReference the desired bitmask (default=0x0) */ public void setBackStencilReference(int backStencilReference) { this.backStencilReference = backStencilReference; } /** - * Returns the front stencil mask - * @return + * Returns the front stencil mask. + * + * @return the bitmask applied before comparing the front stencil to its + * reference value */ public int getFrontStencilMask() { return frontStencilMask; } /** - * Returns the front stencil reference - * @return + * Returns the front stencil reference. + * + * @return the reference value for the front stencil */ public int getFrontStencilReference() { return frontStencilReference; } /** - * Returns the back stencil mask - * @return + * Returns the back stencil mask. + * + * @return the bitmask applied before comparing the back stencil to its + * reference value */ public int getBackStencilMask() { return backStencilMask; } /** - * Returns the back stencil reference - * @return + * Returns the back stencil reference. + * + * @return the reference value for the back stencil */ public int getBackStencilReference() { return backStencilReference; } /** - * Retrieve the blend equation. + * Returns the blend equation. * * @return the blend equation. */ @@ -1226,7 +1240,7 @@ public BlendEquation getBlendEquation() { } /** - * Retrieve the blend equation used for the alpha component. + * Returns the blend equation used for the alpha component. * * @return the blend equation for the alpha component. */ @@ -1235,7 +1249,7 @@ public BlendEquationAlpha getBlendEquationAlpha() { } /** - * Retrieve the blend mode. + * Returns the blend mode. * * @return the blend mode. */ @@ -1244,7 +1258,7 @@ public BlendMode getBlendMode() { } /** - * Provides the source factor for the RGB components in + * Returns the source factor for the RGB components in * BlendMode.Custom. * * @return the custom source factor for RGB components. @@ -1254,7 +1268,7 @@ public BlendFunc getCustomSfactorRGB() { } /** - * Provides the destination factor for the RGB components in + * Returns the destination factor for the RGB components in * BlendMode.Custom. * * @return the custom destination factor for RGB components. @@ -1264,7 +1278,7 @@ public BlendFunc getCustomDfactorRGB() { } /** - * Provides the source factor for the alpha component in + * Returns the source factor for the alpha component in * BlendMode.Custom. * * @return the custom destination factor for alpha component. @@ -1274,7 +1288,7 @@ public BlendFunc getCustomSfactorAlpha() { } /** - * Provides the destination factor for the alpha component in + * Returns the destination factor for the alpha component in * BlendMode.Custom. * * @return the custom destination factor for alpha component. diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix3f.java b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java index bed5008c5b8..5b4d6d2634f 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java @@ -767,7 +767,8 @@ public void loadIdentity() { /** * Tests for exact identity. The matrix is unaffected. * - * @return true if equal to {@link #IDENTITY}, otherwise false + * @return true if all diagonals = 1 and all other elements = 0 or -0, + * otherwise false */ public boolean isIdentity() { return (m00 == 1 && m01 == 0 && m02 == 0) @@ -1177,7 +1178,7 @@ public Matrix3f transposeNew() { /** * Returns a string representation of the matrix, which is unaffected. For - * example, an identity matrix would be represented by: + * example, the identity matrix is represented by: *

      * Matrix3f
      * [
@@ -1187,7 +1188,7 @@ public Matrix3f transposeNew() {
      * ]
      * 
* - * @return the string representation + * @return the string representation (not null, not empty) */ @Override public String toString() { @@ -1217,10 +1218,10 @@ public String toString() { } /** - * Returns a hash code. If two matrices are logically equivalent, they will - * return the same hash code. The current instance is unaffected. + * Returns a hash code. If two matrices have identical values, they will + * have the same hash code. The matrix is unaffected. * - * @return the hash-code value + * @return a 32-bit value for use in hashing * @see java.lang.Object#hashCode() */ @Override @@ -1242,11 +1243,13 @@ public int hashCode() { } /** - * Tests for exact equality with the argument, distinguishing -0 from 0. The - * current instance is unaffected. + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code o} is null, false is returned. Either way, the current instance is + * unaffected. * * @param o the object to compare (may be null, unaffected) - * @return true if equal, otherwise false + * @return true if {@code this} and {@code o} have identical values, + * otherwise false */ @Override public boolean equals(Object o) { diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java index 99280a826d0..955a4d50fbf 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,15 +39,15 @@ import java.util.logging.Logger; /** - * Matrix4f represents a single-precision 4x4 matrix for use as a - * 3-D coordinate transform or perspective transform. It provides convenience - * methods for loading data from many sources. + * A 4x4 matrix composed of 16 single-precision elements, used to represent + * linear or perspective transformations of 3-D coordinates. * - * The rightmost column (column 3) stores the translation vector. Element + *

The rightmost column (column 3) stores the translation vector. Element * numbering is (row,column), so m03 is the row 0, column 3, which is the X - * translation. This means that the implicit storage order is column-major. - * However, the get() and set() functions on float arrays default to row-major - * order! + * translation. + * + *

Methods with names ending in "Local" modify the current instance. They are + * used to avoid creating garbage. * * @author Mark Powell * @author Joshua Slack @@ -58,67 +58,67 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable private static final Logger logger = Logger.getLogger(Matrix4f.class.getName()); /** - * the element in row 0, column 0 + * The element in row 0, column 0. */ public float m00; /** - * the element in row 0, column 1 + * The element in row 0, column 1. */ public float m01; /** - * the element in row 0, column 2 + * The element in row 0, column 2. */ public float m02; /** - * the element in row 0, column 3 (the X translation) + * The element in row 0, column 3 (the X translation). */ public float m03; /** - * the element in row 1, column 0 + * The element in row 1, column 0. */ public float m10; /** - * the element in row 1, column 1 + * The element in row 1, column 1. */ public float m11; /** - * the element in row 1, column 2 + * The element in row 1, column 2. */ public float m12; /** - * the element in row 1, column 3 (the Y translation) + * The element in row 1, column 3 (the Y translation). */ public float m13; /** - * the element in row 2, column 0 + * The element in row 2, column 0. */ public float m20; /** - * the element in row 2, column 1 + * The element in row 2, column 1. */ public float m21; /** - * the element in row 2, column 2 + * The element in row 2, column 2. */ public float m22; /** - * the element in row 2, column 3 (the Z translation) + * The element in row 2, column 3 (the Z translation). */ public float m23; /** - * the element in row 3, column 0 + * The element in row 3, column 0. */ public float m30; /** - * the element in row 3, column 1 + * The element in row 3, column 1. */ public float m31; /** - * the element in row 3, column 2 + * The element in row 3, column 2. */ public float m32; /** - * the element in row 3, column 3 + * The element in row 3, column 3. */ public float m33; /** @@ -131,8 +131,7 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable public static final Matrix4f IDENTITY = new Matrix4f(); /** - * Create a Matrix4f initialized to identity (diagonals = 1, - * other elements = 0). + * Instantiates an identity matrix (diagonals = 1, other elements = 0). */ public Matrix4f() { loadIdentity(); @@ -933,7 +932,7 @@ public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { } /** - * Load identity (diagonals = 1, other elements = 0). + * Configures as an identity matrix (diagonals = 1, other elements = 0). */ public void loadIdentity() { m01 = m02 = m03 = 0.0f; @@ -1040,9 +1039,9 @@ public void fromAngleNormalAxis(float angle, Vector3f axis) { } /** - * Multiply in place, by the specified scalar. + * Multiplies in place by the scalar argument. * - * @param scalar the scale factor to apply to all elements + * @param scalar the scaling factor to apply to all elements */ public void multLocal(float scalar) { m00 *= scalar; @@ -1541,9 +1540,10 @@ public Matrix4f invert(Matrix4f store) { } /** - * Invert in place. + * Inverts in place. If the current instance is singular, the matrix is + * zeroed. * - * @return this matrix (inverted) + * @return the (inverted) current instance (for chaining) */ public Matrix4f invertLocal() { @@ -1717,9 +1717,9 @@ public float determinant() { } /** - * Set all elements to zero. + * Sets all elements to zero. * - * @return this (zeroed) + * @return the (modified) current instance (for chaining) */ public Matrix4f zero() { m00 = m01 = m02 = m03 = 0.0f; @@ -1790,10 +1790,10 @@ public Vector3f toTranslationVector() { } /** - * Determine the translation component of this 3-D coordinate transform. + * Returns the translation component of the coordinate transform. * * @param vector storage for the result (not null, modified) - * @return vector + * @return the translation component (in {@code vector}) for chaining */ public Vector3f toTranslationVector(Vector3f vector) { return vector.set(m03, m13, m23); @@ -1811,10 +1811,10 @@ public Quaternion toRotationQuat() { } /** - * Determine the rotation component of this 3-D coordinate transform. + * Returns the rotation component of the coordinate transform. * * @param q storage for the result (not null, modified) - * @return q + * @return the rotation component (in {@code q}) for chaining */ public Quaternion toRotationQuat(Quaternion q) { return q.fromRotationMatrix(m00, m01, m02, m10, @@ -1831,7 +1831,7 @@ public Matrix3f toRotationMatrix() { } /** - * Determine the rotation component of this 3-D coordinate transform. + * Determines the rotation component of the coordinate transform. * * @param mat storage for the result (not null, modified) */ @@ -1859,10 +1859,10 @@ public Vector3f toScaleVector() { } /** - * Determine the scale component of this 3-D coordinate transform. + * Determines the scale component of the coordinate transform. * * @param store storage for the result (not null, modified) - * @return store + * @return the scale factors (in {@code store}) for chaining */ public Vector3f toScaleVector(Vector3f store) { float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); @@ -1873,7 +1873,7 @@ public Vector3f toScaleVector(Vector3f store) { } /** - * Alter the scale component of this 3-D coordinate transform. + * Alters the scale component of the coordinate transform. * * @param x the desired scale factor for the X axis * @param y the desired scale factor for the Y axis @@ -1907,7 +1907,7 @@ public void setScale(float x, float y, float z) { } /** - * Alter the scale component of this 3-D coordinate transform. + * Alters the scale component of the coordinate transform. * * @param scale the desired scale factors (not null, unaffected) */ @@ -1946,7 +1946,7 @@ public void setTranslation(float x, float y, float z) { } /** - * Alter the translation component of this 3-D coordinate transform. + * Alters the translation component of the coordinate transform. * * @param translation the desired translation (not null, unaffected) */ @@ -2142,7 +2142,8 @@ public void rotateVect(Vector3f vec) { } /** - * Represent as a String. For example, identity would be represented by: + * Returns a string representation of the matrix, which is unaffected. For + * example, the identity matrix is represented by: *

      * Matrix4f
      * [
@@ -2153,7 +2154,7 @@ public void rotateVect(Vector3f vec) {
      * ]
      * 
* - * @return the string representation of this object. + * @return the string representation (not null, not empty) */ @Override public String toString() { @@ -2198,11 +2199,10 @@ public String toString() { } /** - * hashCode returns the hash code value as an integer and is - * supported for the benefit of hashing based collection classes such as - * Hashtable, HashMap, HashSet etc. + * Returns a hash code. If two matrices have identical values, they will + * have the same hash code. The matrix is unaffected. * - * @return the hashcode for this instance of Matrix4f. + * @return a 32-bit value for use in hashing * @see java.lang.Object#hashCode() */ @Override @@ -2232,10 +2232,13 @@ public int hashCode() { } /** - * Test for equality with the specified object. + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code o} is null, false is returned. Either way, the current instance is + * unaffected. * - * @param o the object to compare, or null - * @return true if this and o are equal, otherwise false + * @param o the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code o} have identical values, + * otherwise false */ @Override public boolean equals(Object o) { @@ -2458,7 +2461,7 @@ public void multLocal(Quaternion rotation) { } /** - * Create a copy. + * Creates a copy. The current instance is unaffected. * * @return a new instance with the same element values */ 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 52247cd1d74..0974a38b7e6 100644 --- a/jme3-core/src/main/java/com/jme3/math/Quaternion.java +++ b/jme3-core/src/main/java/com/jme3/math/Quaternion.java @@ -103,7 +103,7 @@ public Quaternion() { } /** - * Instantiates a quaternion with specified components. + * Instantiates a quaternion with the specified components. * * @param x the desired X component * @param y the desired Y component @@ -930,7 +930,7 @@ public Quaternion add(Quaternion q) { * * @param q the quaternion to add (not null, unaffected unless it's * {@code this}) - * @return the (modified) current instance + * @return the (modified) current instance (for chaining) */ public Quaternion addLocal(Quaternion q) { this.x += q.x; @@ -994,7 +994,7 @@ public Quaternion mult(Quaternion q) { * However, if {@code this} and {@code storeResult} are the same object, the result * is undefined. * - * @param q the right factor (not null, unaffected unless it's {@code res}) + * @param q the right factor (not null, unaffected unless it's {@code storeResult}) * @param storeResult storage for the product, or null for a new Quaternion * @return {@code this * q} (either {@code storeResult} or a new Quaternion) */ @@ -1348,13 +1348,13 @@ public Quaternion negateLocal() { } /** - * Returns a string representation. The current instance is unaffected. The - * format is: - * - *

(X.XXXX, Y.YYYY, Z.ZZZZ, W.WWWW) + * Returns a string representation of the quaternion, which is unaffected. + * For example, the identity quaternion is represented by: + *

+     * (0.0, 0.0, 0.0, 1.0)
+     * 
* - * @return the string representation - * @see java.lang.Object#toString() + * @return the string representation (not null, not empty) */ @Override public String toString() { @@ -1362,11 +1362,13 @@ public String toString() { } /** - * Tests for exact equality with the argument, distinguishing -0 from 0. The - * current instance is unaffected. + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code o} is null, false is returned. Either way, the current instance is + * unaffected. * * @param o the object to compare (may be null, unaffected) - * @return true if equal, otherwise false + * @return true if {@code this} and {@code o} have identical values, + * otherwise false */ @Override public boolean equals(Object o) { @@ -1425,10 +1427,10 @@ public boolean isSimilar(Quaternion other, float epsilon) { } /** - * Returns a hash code. If two quaternions are logically equivalent, they - * will return the same hash code. The current instance is unaffected. + * Returns a hash code. If two quaternions have identical values, they + * will have the same hash code. The current instance is unaffected. * - * @return the hash code value + * @return a 32-bit value for use in hashing * @see java.lang.Object#hashCode() */ @Override 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 53e203305be..c172d26d4a2 100644 --- a/jme3-core/src/main/java/com/jme3/math/Transform.java +++ b/jme3-core/src/main/java/com/jme3/math/Transform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -428,8 +428,8 @@ public boolean isIdentity() { } /** - * Returns a hash code. If two transforms are logically equivalent, they - * will return the same hash code. The current instance is unaffected. + * Returns a hash code. If two transforms have identical values, they + * will have the same hash code. The current instance is unaffected. * * @return a 32-bit value for use in hashing */ @@ -443,11 +443,13 @@ public int hashCode() { } /** - * Tests for exact equality with the argument, distinguishing -0 from 0. The - * current instance is unaffected. + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code obj} is null, false is returned. Either way, the current instance + * is unaffected. * - * @param obj the object to compare to (may be null, unaffected) - * @return true if the objects are exactly equal, otherwise false + * @param obj the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code obj} have identical values, + * otherwise false */ @Override public boolean equals(Object obj) { @@ -464,12 +466,12 @@ public boolean equals(Object obj) { } /** - * Returns a string representation. The current instance is unaffected. The - * format is: + * Returns a string representation of the transform, which is unaffected. + * For example, the identity transform is represented by: *
-     * Transform[ TX.XXXX, TY.YYYY, TZ.ZZZZ]
-     * [ R.XXXX, R.YYYY, R.ZZZZ, R.WWWW]
-     * [ S.XXXX , S.YYYY, S.ZZZZ]
+     * Transform[ 0.0, 0.0, 0.0]
+     * [ 0.0, 0.0, 0.0, 1.0]
+     * [ 1.0 , 1.0, 1.0]
      * 
* * @return the string representation (not null, not empty) diff --git a/jme3-core/src/main/java/com/jme3/math/Triangle.java b/jme3-core/src/main/java/com/jme3/math/Triangle.java index fed4a30b671..1c21d3b0876 100644 --- a/jme3-core/src/main/java/com/jme3/math/Triangle.java +++ b/jme3-core/src/main/java/com/jme3/math/Triangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,33 +37,43 @@ import java.io.IOException; /** - * Triangle defines a triangle in terms of its vertex locations, - * with auxiliary storage for its centroid, normal vector, projection, and - * index. + * Describes a triangle in terms of its vertex locations, with auxiliary storage + * for its centroid, normal vector, projection, and index. * * @author Mark Powell * @author Joshua Slack */ public class Triangle extends AbstractTriangle implements Savable, Cloneable, java.io.Serializable { static final long serialVersionUID = 1; - + /** + * The location of the first vertex in winding order. + */ private Vector3f pointA = new Vector3f(); + /** + * The location of the 2nd vertex in winding order. + */ private Vector3f pointB = new Vector3f(); + /** + * The location of the 3rd vertex in winding order. + */ private Vector3f pointC = new Vector3f(); private transient Vector3f center; private transient Vector3f normal; private float projection; + /** + * The index of the triangle, used to identify it in an OBBTree. + */ private int index; /** - * Instantiate a zero-size Triangle at the origin. + * Instantiate a zero-size triangle at the origin. */ public Triangle() { } /** - * Instantiate a Triangle with the specified vertex locations. - * Vertices should be listed in the desired winding order, typically + * Instantiates a triangle with the specified vertex locations. Vertices + * should be listed in the desired winding order, typically * counter-clockwise. * * @param p1 the location of the first vertex (not null, unaffected) @@ -77,7 +87,7 @@ public Triangle(Vector3f p1, Vector3f p2, Vector3f p3) { } /** - * Access the location of the indexed vertex. + * Accesses the location of the indexed vertex. * * @param i the index of the vertex to access (0, 1, or 2) * @return a pre-existing location vector, or null if the index is invalid @@ -96,7 +106,7 @@ public Vector3f get(int i) { } /** - * Access the location of the first vertex. + * Accesses the location of the first vertex. * * @return the pre-existing location vector (not null) */ @@ -106,7 +116,7 @@ public Vector3f get1() { } /** - * Access the location of the 2nd vertex. + * Accesses the location of the 2nd vertex. * * @return the pre-existing location vector (not null) */ @@ -116,7 +126,7 @@ public Vector3f get2() { } /** - * Access the location of the 3rd vertex. + * Accesses the location of the 3rd vertex. * * @return the pre-existing location vector (not null) */ @@ -126,7 +136,7 @@ public Vector3f get3() { } /** - * Alter the location of the indexed vertex and delete the stored centroid + * Alters the location of the indexed vertex and deletes the stored centroid * and normal. * * @param i the index of the vertex to alter (0, 1, or 2) @@ -150,7 +160,7 @@ public void set(int i, Vector3f point) { } /** - * Alter the location of the indexed vertex and delete the stored centroid + * Alters the location of the indexed vertex and deletes the stored centroid * and normal. * * @param i the index of the vertex to alter (0, 1, or 2) @@ -176,8 +186,8 @@ public void set(int i, float x, float y, float z) { } /** - * Alter the location of the first vertex and delete the stored centroid and - * normal. + * Alters the location of the first vertex and deletes the stored centroid + * and normal. * * @param v the desired location (not null, unaffected) */ @@ -189,7 +199,7 @@ public void set1(Vector3f v) { } /** - * Alter the location of the 2nd vertex and delete the stored centroid and + * Alters the location of the 2nd vertex and deletes the stored centroid and * normal. * * @param v the desired location (not null, unaffected) @@ -202,7 +212,7 @@ public void set2(Vector3f v) { } /** - * Alter the location of the 3rd vertex and delete the stored centroid and + * Alters the location of the 3rd vertex and deletes the stored centroid and * normal. * * @param v the desired location (not null, unaffected) @@ -215,8 +225,8 @@ public void set3(Vector3f v) { } /** - * Alter the locations of all 3 vertices and delete the stored centroid and - * normal. + * Alters the locations of all 3 vertices and deletes the stored centroid + * and normal. * * @param v1 the desired location of the first vertex (not null, unaffected) * @param v2 the desired location of the 2nd vertex (not null, unaffected) @@ -233,7 +243,7 @@ public void set(Vector3f v1, Vector3f v2, Vector3f v3) { } /** - * Recalculate the stored centroid based on the current vertex locations. + * Recalculates the stored centroid based on the current vertex locations. */ public void calculateCenter() { if (center == null) { @@ -245,7 +255,7 @@ public void calculateCenter() { } /** - * Recalculate the stored normal based on the current vertex locations. + * Recalculates the stored normal based on the current vertex locations. */ public void calculateNormal() { if (normal == null) { @@ -258,7 +268,7 @@ public void calculateNormal() { } /** - * Access the stored centroid (the average of the 3 vertex locations) + * Accesses the stored centroid (the average of the 3 vertex locations) * calculating it if it is null. * * @return the coordinates of the center (an internal vector subject to @@ -272,7 +282,7 @@ public Vector3f getCenter() { } /** - * Alter the stored centroid without affecting the stored normal or any + * Alters the stored centroid without affecting the stored normal or any * vertex locations. * * @param center the desired value (alias created if not null) @@ -282,7 +292,7 @@ public void setCenter(Vector3f center) { } /** - * Access the stored normal, updating it if it is null. + * Accesses the stored normal, updating it if it is null. * * @return unit normal vector (an internal vector subject to re-use) */ @@ -294,7 +304,7 @@ public Vector3f getNormal() { } /** - * Alter the stored normal without affecting the stored centroid or any + * Alters the stored normal without affecting the stored centroid or any * vertex locations. * * @param normal the desired value (alias created if not null) @@ -304,7 +314,7 @@ public void setNormal(Vector3f normal) { } /** - * Read the projection of the vertices relative to the line origin. + * Returns the projection of the vertices relative to the line origin. * * @return the stored projection value */ @@ -313,7 +323,7 @@ public float getProjection() { } /** - * Alter the projection of the vertices relative to the line origin. + * Alters the projection of the vertices relative to the line origin. * * @param projection the desired projection value */ @@ -322,7 +332,7 @@ public void setProjection(float projection) { } /** - * Read the index of this triangle, used to identify it in an OBBTree. + * Returns the index of this triangle, used to identify it in an OBBTree. * * @return the stored index */ @@ -331,7 +341,7 @@ public int getIndex() { } /** - * Alter the index of this triangle, used to identify it in an OBBTree. + * Alters the index of this triangle, used to identify it in an OBBTree. * * @param index the desired index */ @@ -351,7 +361,7 @@ public static Vector3f computeTriangleNormal(Vector3f v1, Vector3f v2, Vector3f } /** - * Serialize this triangle to the specified exporter, for example when + * Serializes this triangle to the specified exporter, for example when * saving to a J3O file. * * @param e (not null) @@ -365,7 +375,7 @@ public void write(JmeExporter e) throws IOException { } /** - * De-serialize this triangle from the specified importer, for example when + * De-serializes this triangle from the specified importer, for example when * loading from a J3O file. * * @param importer (not null) @@ -379,7 +389,7 @@ public void read(JmeImporter importer) throws IOException { } /** - * Create a copy of this triangle. + * Creates a copy of this triangle. * * @return a new instance, equivalent to this one */ 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 a6629785216..89efe8a6fd7 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ /** * A vector composed of 3 single-precision components, used to represent - * locations, offsets, and directions in 3-dimensional space. + * locations, offsets, velocities, and directions in 3-dimensional space. * *

Methods with names ending in "Local" modify the current instance. They are * used to cut down on the creation of new instances. @@ -177,12 +177,12 @@ public Vector3f add(Vector3f vec) { /** * Adds a specified vector and returns the sum in a 3rd vector. The current - * instance is unaffected unless it's result. + * instance is unaffected unless it's {@code result}. * * @param vec the vector to add (not null, unaffected unless it's - * result) + * {@code result}) * @param result storage for the sum (not null) - * @return result (for chaining) + * @return {@code result} (for chaining) */ public Vector3f add(Vector3f vec, Vector3f result) { result.x = x + vec.x; @@ -195,7 +195,7 @@ public Vector3f add(Vector3f vec, Vector3f result) { * Adds the argument and returns the (modified) current instance. If the * argument is null, null is returned. * - * @param vec the vector to add (unaffected unless it's this) + * @param vec the vector to add (unaffected unless it's {@code this}) * or null for none * @return the (modified) current instance or null */ @@ -298,7 +298,7 @@ public float dot(Vector3f vec) { * new instance. The current instance is unaffected. * * @param v the right factor (not null, unaffected) - * @return this cross v (a new Vector3f) + * @return {@code this} cross {@code v} (a new Vector3f) */ public Vector3f cross(Vector3f v) { return cross(v, null); @@ -307,13 +307,13 @@ public Vector3f cross(Vector3f v) { /** * Calculates a cross product with a specified vector and returns the * product in a 3rd vector. The current instance is unaffected unless it's - * result. + * {@code result}. * * @param v the right factor (not null, unaffected unless it's - * result) + * {@code result}) * @param result storage for the product, or null for a new Vector3f - * @return this cross v (either - * result or a new Vector3f) + * @return {@code this} cross {@code v} (either {@code result} or a new + * Vector3f) */ public Vector3f cross(Vector3f v, Vector3f result) { return cross(v.x, v.y, v.z, result); @@ -322,14 +322,14 @@ public Vector3f cross(Vector3f v, Vector3f result) { /** * Calculates a cross product with specified components and returns the * product in the specified vector. The current instance is unaffected - * unless it's result. + * unless it's {@code result}. * * @param otherX the X component of the right factor * @param otherY the Y component of the right factor * @param otherZ the Z component of the right factor * @param result storage for the product, or null for a new Vector3f - * @return this cross v (either - * result or a new Vector3f) + * @return {@code this} cross (X,Y,Z) (either {@code result} or a new + * Vector3f) */ public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) { if (result == null) { @@ -489,11 +489,11 @@ public Vector3f mult(float scalar) { /** * Multiplies with the specified scalar and returns the product in the * specified vector. The current instance is unaffected, unless it's - * product. + * {@code product}. * * @param scalar the scaling factor * @param product storage for the product, or null for a new Vector3f - * @return either product or a new Vector3f + * @return either {@code product} or a new Vector3f */ public Vector3f mult(float scalar, Vector3f product) { if (null == product) { @@ -523,7 +523,7 @@ public Vector3f multLocal(float scalar) { * Multiplies component-wise by the argument and returns the (modified) * current instance. If the argument is null, null is returned. * - * @param vec the scale vector (unaffected unless it's this) or + * @param vec the scale vector (unaffected unless it's {@code this}) or * null for none * @return the (modified) current instance (for chaining) or null */ @@ -587,12 +587,12 @@ public Vector3f mult(float x, float y, float z) { * Multiplies component-wise with the specified vector and returns the * product in a 3rd vector. If the argument is null, null is returned. * Either way, the current instance is unaffected, unless it's - * store. + * {@code store}. * - * @param vec the scale vector (unaffected unless it's store) + * @param vec the scale vector (unaffected unless it's {@code store}) * or null for none * @param store storage for the product, or null for a new Vector3f - * @return either store or a new Vector3f or null + * @return either {@code store} or a new Vector3f or null */ public Vector3f mult(Vector3f vec, Vector3f store) { if (null == vec) { @@ -659,8 +659,8 @@ public Vector3f divide(Vector3f divisor) { } /** - * Divides component-wise by the specified components and returns the quotient - * as a new instance. The current instance is unaffected. + * Divides component-wise by the specified components and returns the + * quotient as a new instance. The current instance is unaffected. * * @param x the divisor for the X component * @param y the divisor for the Y component @@ -675,7 +675,8 @@ public Vector3f divide(float x, float y, float z) { * Divides component-wise by the argument and returns the (modified) current * instance. * - * @param divisor the divisor (not null, unaffected) + * @param divisor the divisor (not null, unaffected unless it's + * {@code this}) * @return the (modified) current instance (for chaining) */ public Vector3f divideLocal(Vector3f divisor) { @@ -721,8 +722,8 @@ public Vector3f subtract(Vector3f vec) { * Subtracts the argument and returns the (modified) current instance. If * the argument is null, null is returned. * - * @param vec the vector to subtract (unaffected unless it's - * this) or null for none + * @param vec the vector to subtract (unaffected unless it's {@code this}) + * or null for none * @return the (modified) current instance or null */ public Vector3f subtractLocal(Vector3f vec) { @@ -739,12 +740,12 @@ public Vector3f subtractLocal(Vector3f vec) { /** * Subtracts the specified vector and returns the difference in a 3rd * vector. The current instance is unaffected unless it's - * result. + * {@code result}. * * @param vec the vector to subtract (not null, unaffected unless it's - * result) + * {@code result}) * @param result storage for the difference, or null for a new Vector3f - * @return either result or a new Vector3f + * @return either {@code result} or a new Vector3f */ public Vector3f subtract(Vector3f vec, Vector3f result) { if (result == null) { @@ -1010,7 +1011,8 @@ public float[] toArray(float[] floats) { * unaffected. * * @param o the object to compare (may be null, unaffected) - * @return true if equal, otherwise false + * @return true if {@code this} and {@code o} have identical values, + * otherwise false */ @Override public boolean equals(Object o) { @@ -1061,10 +1063,10 @@ public boolean isSimilar(Vector3f other, float epsilon) { } /** - * Returns a hash code. If two vectors are logically equivalent, they will - * return the same hash code. The current instance is unaffected. + * Returns a hash code. If two vectors have identical values, they will + * have the same hash code. The current instance is unaffected. * - * @return the hash code value + * @return a 32-bit value for use in hashing */ @Override public int hashCode() { @@ -1076,12 +1078,13 @@ public int hashCode() { } /** - * Returns a string representation. The current instance is unaffected. The - * format is: - * - *

(XX.XXXX, YY.YYYY, ZZ.ZZZZ) + * Returns a string representation of the vector, which is unaffected. + * For example, the +X direction vector is represented by: + *

+     * (1.0, 0.0, 0.0)
+     * 
* - * @return the string representation + * @return the string representation (not null, not empty) */ @Override public String toString() { diff --git a/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java b/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java index 63113c729d9..cdd8fe8d47e 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java @@ -33,19 +33,20 @@ public interface JmeDialogsFactory { /** - * Set a function to handler app settings. - * The default implementation shows a settings dialog if available. - * @param handler handler function that accepts as argument an instance of AppSettings - * to transform and a boolean with the value of true if the settings are expected to be loaded from - * the user registry. The handler function returns false if the configuration is interrupted (eg.the the dialog was closed) - * or true otherwise. + * Set a function to handle app settings. The default implementation shows a + * settings dialog if available. + * + * @param settings the settings object to edit + * @param loadFromRegistry if true, copy the settings, otherwise merge them + * @return true to continue, false to exit the application */ public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry); /** - * Set function to handle errors. - * The default implementation show a dialog if available. - * @param handler Consumer to which the error is passed as String + * Set function to handle errors. The default implementation show a dialog + * if available. + * + * @param message text to be displayed in the dialog */ public void showErrorDialog(String message); } From b47081525995979c898721eba19d630e7d205320 Mon Sep 17 00:00:00 2001 From: manuelrmo <118840772+manuelrmo@users.noreply.github.com> Date: Wed, 14 Dec 2022 23:30:50 -0800 Subject: [PATCH 049/140] Implementation of a glTF extension loader for KHR_texture_transform (#1869) * Implementation of a glTF extension loader for KHR_texture_transform * Thread-safe version of the glTF extension loader for KHR_texture_transform * Updated thread-safe version of the glTF extension loader for KHR_texture_transform * Fix (switched indices of the translation matrix): thread-safe version of the glTF extension loader for KHR_texture_transform * Added support for texCoord, fixed matrix comparison * Simplified matrix comparison, removed trailing whitespaces * Update to differentiate transformations applied to different UV sets * Improved memory usage for transformMap * Specified Map generic types & removed unnecessary cast Co-authored-by: Manuel --- .../plugins/gltf/CustomContentManager.java | 3 +- .../jme3/scene/plugins/gltf/GltfLoader.java | 1 + .../gltf/TextureTransformExtensionLoader.java | 164 ++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java 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 5b8908948ca..94f632d7e38 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-2020 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,6 +57,7 @@ public class CustomContentManager { defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", new PBRSpecGlossExtensionLoader()); defaultExtensionLoaders.put("KHR_lights_punctual", new LightsPunctualExtensionLoader()); defaultExtensionLoaders.put("KHR_materials_unlit", new UnlitExtensionLoader()); + defaultExtensionLoaders.put("KHR_texture_transform", new TextureTransformExtensionLoader()); } void init(GltfLoader gltfLoader) { 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 6a0c9d5ae47..523ac02fd59 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 @@ -382,6 +382,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { for (JsonElement primitive : primitives) { JsonObject meshObject = primitive.getAsJsonObject(); Mesh mesh = new Mesh(); + addToCache("mesh", 0, mesh, 1); Integer mode = getAsInteger(meshObject, "mode"); mesh.setMode(getMeshMode(mode)); Integer indices = getAsInteger(meshObject, "indices"); 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 new file mode 100644 index 00000000000..d54382d8ac1 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +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; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger; +import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferType; +import com.jme3.texture.Texture2D; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Thread-safe extension loader for KHR_texture_transform. + * It allows for UV coordinates to be scaled/rotated/translated + * based on transformation properties from textures in the glTF model. + * + * See spec at https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform + * + * @author manuelrmo - Created on 11/20/2022 + */ +public class TextureTransformExtensionLoader implements ExtensionLoader { + + private final static Logger logger = Logger.getLogger(TextureTransformExtensionLoader.class.getName()); + + /** + * Scale/rotate/translate UV coordinates based on a transformation matrix. + * Code adapted from scaleTextureCoordinates(Vector2f) in jme3-core/src/main/java/com/jme3/scene/Mesh.java + * @param mesh The mesh holding the UV coordinates + * @param transform The matrix containing the scale/rotate/translate transformations + * @param verType The vertex buffer type from which to retrieve the UV coordinates + */ + private void uvTransform(Mesh mesh, Matrix3f transform, VertexBuffer.Type verType) { + if (!transform.isIdentity()) { // if transform is the identity matrix, there's nothing to do + VertexBuffer tc = mesh.getBuffer(verType); + if (tc == null) { + throw new IllegalStateException("The mesh has no texture coordinates"); + } + if (tc.getFormat() != VertexBuffer.Format.Float) { + throw new UnsupportedOperationException("Only float texture coord format is supported"); + } + if (tc.getNumComponents() != 2) { + throw new UnsupportedOperationException("Only 2D texture coords are supported"); + } + FloatBuffer fb = (FloatBuffer) tc.getData(); + fb.clear(); + for (int i = 0; i < fb.limit() / 2; i++) { + float x = fb.get(); + float y = fb.get(); + fb.position(fb.position() - 2); + Vector3f v = transform.mult(new Vector3f(x, y, 1)); + fb.put(v.getX()).put(v.getY()); + } + fb.clear(); + tc.updateData(fb); + } + } + + // The algorithm relies on the fact that the GltfLoader.class object + // loads all textures of a given mesh before doing so for the next mesh. + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException { + if (!(input instanceof Texture2D)) { + logger.log(Level.WARNING, "KHR_texture_transform extension added on an unsupported element, the loaded scene result will be unexpected."); + } + Mesh mesh = loader.fetchFromCache("mesh", 0, Mesh.class); + if (mesh != null) { + Matrix3f translation = new Matrix3f(); + Matrix3f rotation = new Matrix3f(); + Matrix3f scale = new Matrix3f(); + Integer texCoord = getAsInteger(parent.getAsJsonObject(), "texCoord"); + texCoord = texCoord != null ? texCoord : 0; + JsonObject jsonObject = extension.getAsJsonObject(); + if (jsonObject.has("offset")) { + JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); + translation.set(0, 2, jsonArray.get(0).getAsFloat()); + translation.set(1, 2, jsonArray.get(1).getAsFloat()); + } + if (jsonObject.has("rotation")) { + float rad = jsonObject.get("rotation").getAsFloat(); + rotation.set(0, 0, (float) Math.cos(rad)); + rotation.set(0, 1, (float) Math.sin(rad)); + rotation.set(1, 0, (float) -Math.sin(rad)); + rotation.set(1, 1, (float) Math.cos(rad)); + } + if (jsonObject.has("scale")) { + JsonArray jsonArray = jsonObject.getAsJsonArray("scale"); + scale.set(0, 0, jsonArray.get(0).getAsFloat()); + scale.set(1, 1, jsonArray.get(1).getAsFloat()); + } + if (jsonObject.has("texCoord")) { + texCoord = jsonObject.get("texCoord").getAsInt(); // it overrides the parent's texCoord value + } + Matrix3f transform = translation.mult(rotation).mult(scale); + Mesh meshLast = loader.fetchFromCache("textureTransformData", 0, Mesh.class); + Map transformMap = loader.fetchFromCache("textureTransformData", 1, HashMap.class); + if (mesh != meshLast || (transformMap != null && transformMap.get(texCoord) == null)) { + // at this point, we're processing a new mesh or the same mesh as before but for a different UV set + if (mesh != meshLast) { // it's a new mesh + loader.addToCache("textureTransformData", 0, mesh, 2); + if (transformMap == null) { + transformMap = new HashMap<>(); // initialize transformMap + loader.addToCache("textureTransformData", 1, transformMap, 2); + } else { + transformMap.clear(); // reset transformMap + } + } + transformMap.put(texCoord, transform); // store the transformation matrix applied to this UV set + uvTransform(mesh, transform, getVertexBufferType("TEXCOORD_" + texCoord)); + logger.log(Level.FINE, "KHR_texture_transform extension successfully applied."); + } + else { + // at this point, we're processing the same mesh as before for an already transformed UV set + Matrix3f transformLast = transformMap.get(texCoord); + if (!transform.equals(transformLast)) { + logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same mesh's UVs is not supported, the loaded scene result will be unexpected."); + } + } + return input; + } + else { + throw new AssetLoadException("KHR_texture_transform extension applied to a null mesh."); + } + } +} From fa455b886f5eb51d32e921c63a79ac3b6f3ff0ab Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 16 Dec 2022 14:01:37 -0800 Subject: [PATCH 050/140] jme3-core: test the com.jme3.math.Triangle class --- .../test/java/com/jme3/math/TriangleTest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/math/TriangleTest.java diff --git a/jme3-core/src/test/java/com/jme3/math/TriangleTest.java b/jme3-core/src/test/java/com/jme3/math/TriangleTest.java new file mode 100644 index 00000000000..02066f4cf39 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/TriangleTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 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; + +/** + * Verifies that the {@link Triangle} class works correctly. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TriangleTest { + /** + * Basic functionality of a Triangle. + */ + @Test + public void test1() { + Triangle triangle1 = new Triangle( + new Vector3f(1f, 2f, 3f), + new Vector3f(6f, 5f, 4f), + new Vector3f(5f, 8f, 2f) + ); + + // Verify that centroid and normal are calculated correctly. + Vector3f c1 = triangle1.getCenter(); + Assert.assertEquals(4f, c1.x, 1e-6f); + Assert.assertEquals(5f, c1.y, 1e-6f); + Assert.assertEquals(3f, c1.z, 1e-6f); + + Vector3f n1 = triangle1.getNormal(); + Assert.assertEquals(-0.408248, n1.x, 1e-6f); + Assert.assertEquals(0.408248, n1.y, 1e-6f); + Assert.assertEquals(0.816497, n1.z, 1e-6f); + + // Clone triangle1 and verify its vertices. + Triangle triangle2 = triangle1.clone(); + Assert.assertTrue(triangle1 != triangle2); + Assert.assertEquals(triangle1.get1(), triangle2.get1()); + Assert.assertEquals(triangle1.get2(), triangle2.get2()); + Assert.assertEquals(triangle1.get3(), triangle2.get3()); + + // Modify triangle1 and verify its new centroid. + triangle1.set1(new Vector3f(-2f, -1f, 0f)); + c1 = triangle1.getCenter(); + Assert.assertEquals(3f, c1.x, 1e-6f); + Assert.assertEquals(4f, c1.y, 1e-6f); + Assert.assertEquals(2f, c1.z, 1e-6f); + + // Verify that triangle2's centroid and normal are (still) correct. + Vector3f c2 = triangle2.getCenter(); + Assert.assertEquals(4f, c2.x, 1e-6f); + Assert.assertEquals(5f, c2.y, 1e-6f); + Assert.assertEquals(3f, c2.z, 1e-6f); + + Vector3f n2 = triangle2.getNormal(); + Assert.assertEquals(-0.408248, n2.x, 1e-6f); + Assert.assertEquals(0.408248, n2.y, 1e-6f); + Assert.assertEquals(0.816497, n2.z, 1e-6f); + } +} From 8de08ad1de9ebbb3ea22b4124bc915762b6de0a7 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 16 Dec 2022 14:02:30 -0800 Subject: [PATCH 051/140] bugfix: Mesh.getTriangle() may yield an incorrect centroid --- jme3-core/src/main/java/com/jme3/scene/Mesh.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 05f7a7a8a3c..fb5a6f7dfba 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -931,6 +931,7 @@ public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) { public void getTriangle(int index, Triangle tri) { getTriangle(index, tri.get1(), tri.get2(), tri.get3()); tri.setIndex(index); + tri.setCenter(null); // invalidate previously cached centroid, if any tri.setNormal(null); } From 5d1ee2624b6b5f203651f9dbd7fcdcc8a0e2067b Mon Sep 17 00:00:00 2001 From: Michael Zuegg Date: Sat, 17 Dec 2022 14:39:27 +0100 Subject: [PATCH 052/140] Fix #1867 (LightFilter gets applied even if not needed) (#1872) * Add NullLightFilter.java * Add usage of null light filter when rendering shadowmaps * Fix formatting * Make static NullLightFilter final * Fix formatting and author --- .../java/com/jme3/light/NullLightFilter.java | 53 +++++++++++++++++++ .../jme3/shadow/AbstractShadowRenderer.java | 10 +++- .../com/jme3/shadow/BasicShadowRenderer.java | 8 ++- .../com/jme3/shadow/PssmShadowRenderer.java | 9 +++- .../jme3/shadow/AbstractShadowRendererVR.java | 9 +++- 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/light/NullLightFilter.java diff --git a/jme3-core/src/main/java/com/jme3/light/NullLightFilter.java b/jme3-core/src/main/java/com/jme3/light/NullLightFilter.java new file mode 100644 index 00000000000..55f46d18027 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/NullLightFilter.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2022 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.light; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +/** + * NullLightFilter does nothing. Used when you want + * to disable the light filter + * + * @author Michael Zuegg + */ +public class NullLightFilter implements LightFilter { + @Override + public void setCamera(Camera camera) { + + } + + @Override + public void filterLights(Geometry geometry, LightList filteredLightList) { + + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java index 384630b521b..65cb39bc303 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -33,6 +33,8 @@ import com.jme3.asset.AssetManager; import com.jme3.export.*; +import com.jme3.light.LightFilter; +import com.jme3.light.NullLightFilter; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; @@ -77,7 +79,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable, JmeCloneable, Cloneable { protected static final Logger logger = Logger.getLogger(AbstractShadowRenderer.class.getName()); - + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); protected int nbShadowMaps = 1; protected float shadowMapSize; protected float shadowIntensity = 0.7f; @@ -443,10 +445,14 @@ protected void renderShadowMap(int shadowMapIndex) { renderManager.getRenderer().clearBuffers(true, true, true); renderManager.setForcedRenderState(forcedRenderState); - // render shadow casters to shadow map + // render shadow casters to shadow map and disables the lightfilter + LightFilter tmpLightFilter = renderManager.getLightFilter(); + renderManager.setLightFilter(NULL_LIGHT_FILTER); viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); + renderManager.setLightFilter(tmpLightFilter); renderManager.setForcedRenderState(null); } + boolean debugfrustums = false; public void displayFrustum() { 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 dcd49be09a7..886abc45ece 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java @@ -32,6 +32,8 @@ package com.jme3.shadow; import com.jme3.asset.AssetManager; +import com.jme3.light.LightFilter; +import com.jme3.light.NullLightFilter; import com.jme3.material.Material; import com.jme3.math.Vector3f; import com.jme3.post.SceneProcessor; @@ -59,7 +61,7 @@ */ @Deprecated public class BasicShadowRenderer implements SceneProcessor { - + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); private RenderManager renderManager; private ViewPort viewPort; final private FrameBuffer shadowFB; @@ -196,7 +198,11 @@ public void postQueue(RenderQueue rq) { r.setFrameBuffer(shadowFB); r.clearBuffers(true, true, true); + // render shadow casters to shadow map and disables the lightfilter + LightFilter tmpLightFilter = renderManager.getLightFilter(); + renderManager.setLightFilter(NULL_LIGHT_FILTER); viewPort.getQueue().renderShadowQueue(shadowOccluders, renderManager, shadowCam, true); + renderManager.setLightFilter(tmpLightFilter); r.setFrameBuffer(viewPort.getOutputFrameBuffer()); renderManager.setForcedMaterial(null); diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java index 7612342404d..a0d405000b7 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java @@ -32,6 +32,8 @@ package com.jme3.shadow; import com.jme3.asset.AssetManager; +import com.jme3.light.LightFilter; +import com.jme3.light.NullLightFilter; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Matrix4f; @@ -75,7 +77,7 @@ */ @Deprecated public class PssmShadowRenderer implements SceneProcessor { - + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); /** * FilterMode specifies how shadows are filtered * @deprecated use {@link EdgeFilteringMode} @@ -448,8 +450,11 @@ public void postQueue(RenderQueue rq) { r.setFrameBuffer(shadowFB[i]); r.clearBuffers(true, true, true); - // render shadow casters to shadow map + // render shadow casters to shadow map and disables the lightfilter + LightFilter tmpLightFilter = renderManager.getLightFilter(); + renderManager.setLightFilter(NULL_LIGHT_FILTER); viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true); + renderManager.setLightFilter(tmpLightFilter); } debugfrustums = false; diff --git a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java b/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java index 4b545daf29c..7338864dd86 100644 --- a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java +++ b/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java @@ -34,6 +34,8 @@ import com.jme3.asset.AssetManager; import com.jme3.export.*; +import com.jme3.light.LightFilter; +import com.jme3.light.NullLightFilter; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; @@ -75,7 +77,7 @@ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr */ public abstract class AbstractShadowRendererVR implements SceneProcessor, Savable { - + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); protected int nbShadowMaps = 1; protected float shadowMapSize; protected float shadowIntensity = 0.7f; @@ -440,8 +442,11 @@ protected void renderShadowMap(int shadowMapIndex) { renderManager.getRenderer().clearBuffers(true, true, true); renderManager.setForcedRenderState(forcedRenderState); - // render shadow casters to shadow map + // render shadow casters to shadow map and disables the lightfilter + LightFilter tmpLightFilter = renderManager.getLightFilter(); + renderManager.setLightFilter(NULL_LIGHT_FILTER); viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); + renderManager.setLightFilter(tmpLightFilter); renderManager.setForcedRenderState(null); } boolean debugfrustums = false; From 478af32acb8b9121b6852d61de198b063163d45e Mon Sep 17 00:00:00 2001 From: Noeri Huisman <8823461+mrxz@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:58:34 +0100 Subject: [PATCH 053/140] BlendableAction: Fix JavaDoc for setMaxTransitionWeight & replace assert with IllegalArgumentException (#1881) --- .../java/com/jme3/anim/tween/action/BlendableAction.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java index ee9ed6470b6..c9882529f9e 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java @@ -114,10 +114,13 @@ protected float getTransitionWeight() { } /** - * @param maxTransitionWeight The max transition weight. Must be >0 and <1 (default=1) + * @param maxTransitionWeight The max transition weight. Must be >=0 and <=1 (default=1) + * @throws IllegalArgumentException If maxTransitionWeight is not between 0 and 1. */ public void setMaxTransitionWeight(double maxTransitionWeight) { - assert maxTransitionWeight >= 0 && maxTransitionWeight <= 1; + if (maxTransitionWeight < 0.0 || maxTransitionWeight > 1.0) { + throw new IllegalArgumentException("maxTransitionWeight must be between 0 and 1"); + } this.maxTransitionWeight = maxTransitionWeight; } From f92a73f91ce1a2852645cd1bb5a4c6b597faf7af Mon Sep 17 00:00:00 2001 From: manuelrmo <118840772+manuelrmo@users.noreply.github.com> Date: Wed, 21 Dec 2022 09:11:08 -0800 Subject: [PATCH 054/140] Fix #1412 (GltfLoader does not support AO packed in MetallicRoughnessMap) (#1880) --- .../java/com/jme3/scene/plugins/gltf/GltfLoader.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 523ac02fd59..b16f042f505 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 @@ -635,6 +635,7 @@ public Material readMaterial(int materialIndex) throws IOException { setDefaultParams(adapter.getMaterial()); } + Integer metallicRoughnessIndex = null; if (pbrMat != null) { adapter.setParam("baseColorFactor", getAsColor(pbrMat, "baseColorFactor", ColorRGBA.White)); adapter.setParam("metallicFactor", getAsFloat(pbrMat, "metallicFactor", 1f)); @@ -642,6 +643,8 @@ public Material readMaterial(int materialIndex) throws IOException { adapter.setParam("baseColorTexture", readTexture(pbrMat.getAsJsonObject("baseColorTexture"))); adapter.setParam("metallicRoughnessTexture", readTexture(pbrMat.getAsJsonObject("metallicRoughnessTexture"))); + JsonObject metallicRoughnessJson = pbrMat.getAsJsonObject("metallicRoughnessTexture"); + metallicRoughnessIndex = metallicRoughnessJson != null ? getAsInteger(metallicRoughnessJson, "index") : null; } adapter.getMaterial().setName(getAsString(matData, "name")); @@ -657,7 +660,13 @@ public Material readMaterial(int materialIndex) throws IOException { if (normal != null) { useNormalsFlag = true; } - adapter.setParam("occlusionTexture", readTexture(matData.getAsJsonObject("occlusionTexture"))); + JsonObject occlusionJson = matData.getAsJsonObject("occlusionTexture"); + Integer occlusionIndex = occlusionJson != null ? getAsInteger(occlusionJson, "index") : null; + if (occlusionIndex != null && occlusionIndex.equals(metallicRoughnessIndex)) { + adapter.getMaterial().setBoolean("AoPackedInMRMap", true); + } else { + adapter.setParam("occlusionTexture", readTexture(matData.getAsJsonObject("occlusionTexture"))); + } adapter.setParam("emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture"))); return adapter.getMaterial(); From 9c3d3636d625820580fd5ed59087eb4bfc1340d4 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 28 Dec 2022 09:00:19 +0330 Subject: [PATCH 055/140] Fix #1882 (J3MLoader always generates mips ignoring MinFilter) (#1884) * Get texture mips generation flag from MinFilter specified in j3m file. * Update copyright date. * Fix J3MLoaderTest failing. * Fix TestMaterialWrite failing. * Update copyright date. * Use Trilinear if no min filter is specified in j3m file. * Add copyright note in J3MOutputCapsule. * Fix J3MLoaderTest failing. --- .../com/jme3/material/plugins/J3MLoader.java | 14 +++++-- .../jme3/material/plugins/J3MLoaderTest.java | 24 ++++++------ .../export/material/J3MOutputCapsule.java | 37 ++++++++++++++++--- .../material/plugin/TestMaterialWrite.java | 5 ++- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index be360213491..0a0c0403c20 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -222,6 +222,7 @@ private Texture parseTextureType(final VarType type, final String value) { // If there is only one token on the value, it must be the path to the texture. if (textureValues.size() == 1) { textureKey = new TextureKey(textureValues.get(0), false); + textureKey.setGenerateMips(true); } else { String texturePath = value.trim(); @@ -256,6 +257,8 @@ private Texture parseTextureType(final VarType type, final String value) { textureKey = new TextureKey(textureValues.get(textureValues.size() - 1), false); } + textureKey.setGenerateMips(true); + // Apply texture options to the texture key if (!textureOptionValues.isEmpty()) { for (final TextureOptionValue textureOptionValue : textureOptionValues) { @@ -276,8 +279,6 @@ private Texture parseTextureType(final VarType type, final String value) { break; } - textureKey.setGenerateMips(true); - Texture texture; try { @@ -861,6 +862,13 @@ private enum TextureOption { * Applies a {@link com.jme3.texture.Texture.MinFilter} to the texture. */ Min { + + @Override + public void applyToTextureKey(final String option, final TextureKey textureKey) { + Texture.MinFilter minFilter = Texture.MinFilter.valueOf(option); + textureKey.setGenerateMips(minFilter.usesMipMapLevels()); + } + @Override public void applyToTexture(final String option, final Texture texture) { texture.setMinFilter(Texture.MinFilter.valueOf(option)); diff --git a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java index 8dc756686ee..7126d94fa86 100644 --- a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java +++ b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java @@ -87,8 +87,8 @@ public void oldStyleTextureParameters_shouldBeSupported() throws Exception { final Texture textureOldStyle = Mockito.mock(Texture.class); final Texture textureOldStyleUsingQuotes = Mockito.mock(Texture.class); - final TextureKey textureKeyUsingQuotes = setupMockForTexture("OldStyleUsingQuotes", "old style using quotes/texture.png", true, textureOldStyleUsingQuotes); - final TextureKey textureKeyOldStyle = setupMockForTexture("OldStyle", "old style/texture.png", true, textureOldStyle); + final TextureKey textureKeyUsingQuotes = setupMockForTexture("OldStyleUsingQuotes", "old style using quotes/texture.png", true, true, textureOldStyleUsingQuotes); + final TextureKey textureKeyOldStyle = setupMockForTexture("OldStyle", "old style/texture.png", true, true, textureOldStyle); j3MLoader.load(assetInfo); @@ -111,14 +111,14 @@ public void newStyleTextureParameters_shouldBeSupported() throws Exception { final Texture textureCombined = Mockito.mock(Texture.class); final Texture textureLooksLikeOldStyle = Mockito.mock(Texture.class); - final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters); - final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip); - setupMockForTexture("Repeat", "repeat.png", false, textureRepeat); - setupMockForTexture("RepeatAxis", "repeat-axis.png", false, textureRepeatAxis); - setupMockForTexture("Min", "min.png", false, textureMin); - setupMockForTexture("Mag", "mag.png", false, textureMag); - setupMockForTexture("Combined", "combined.png", true, textureCombined); - setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, textureLooksLikeOldStyle); + final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, true, textureNoParameters); + final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, true, textureFlip); + setupMockForTexture("Repeat", "repeat.png", false, true, textureRepeat); + setupMockForTexture("RepeatAxis", "repeat-axis.png", false, true, textureRepeatAxis); + setupMockForTexture("Min", "min.png", false, true, textureMin); + setupMockForTexture("Mag", "mag.png", false, true, textureMag); + setupMockForTexture("Combined", "combined.png", true, false, textureCombined); + setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, true, textureLooksLikeOldStyle); j3MLoader.load(assetInfo); @@ -135,11 +135,11 @@ public void newStyleTextureParameters_shouldBeSupported() throws Exception { verify(textureCombined).setWrap(Texture.WrapMode.Repeat); } - private TextureKey setupMockForTexture(final String paramName, final String path, final boolean flipY, final Texture texture) { + private TextureKey setupMockForTexture(final String paramName, final String path, final boolean flipY, boolean generateMips, final Texture texture) { when(materialDef.getMaterialParam(paramName)).thenReturn(new MatParamTexture(VarType.Texture2D, paramName, texture, null)); final TextureKey textureKey = new TextureKey(path, flipY); - textureKey.setGenerateMips(true); + textureKey.setGenerateMips(generateMips); when(assetManager.loadTexture(textureKey)).thenReturn(texture); diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java index fd2c0bbfbdf..950466b4ba4 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2022 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.material.plugin.export.material; import com.jme3.asset.TextureKey; @@ -140,11 +171,7 @@ protected static String formatMatParamTexture(MatParamTexture param) { ret.append(formatWrapMode(tex, Texture.WrapAxis.R)); //Min and Mag filter - Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps; - if (tex.getImage().hasMipmaps() || (key != null && key.isGenerateMips())) { - def = Texture.MinFilter.Trilinear; - } - if (tex.getMinFilter() != def) { + if (tex.getMinFilter() != Texture.MinFilter.Trilinear) { ret.append("Min").append(tex.getMinFilter().name()).append(" "); } diff --git a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java index be8fc3573b2..e5dfa759652 100644 --- a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java +++ b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetKey; import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.material.plugin.export.material.J3MExporter; @@ -76,7 +77,7 @@ public void testWriteMat() throws Exception { mat.setFloat("Shininess", 2.5f); - Texture tex = assetManager.loadTexture("Common/Textures/MissingTexture.png"); + Texture tex = assetManager.loadTexture(new TextureKey("Common/Textures/MissingTexture.png", true)); tex.setMagFilter(Texture.MagFilter.Nearest); tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat); From 1e237f333e95dfb5b00da29310a19c4c724bbddb Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 28 Dec 2022 09:02:41 +0330 Subject: [PATCH 056/140] Made extension loaders non-static to avoid concurrency issues (#1886) Now each GltfLoader instantiated via ThreadLocal will have its own instances of extension loaders. --- .../scene/plugins/gltf/CustomContentManager.java | 6 +++--- .../com/jme3/scene/plugins/gltf/GltfLoader.java | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) 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 94f632d7e38..5e76201819a 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 @@ -51,13 +51,13 @@ public class CustomContentManager { private GltfModelKey key; private GltfLoader gltfLoader; - private static Map defaultExtensionLoaders = new HashMap<>(); + private final Map defaultExtensionLoaders = new HashMap<>(); - static { + public CustomContentManager() { defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", new PBRSpecGlossExtensionLoader()); defaultExtensionLoaders.put("KHR_lights_punctual", new LightsPunctualExtensionLoader()); defaultExtensionLoaders.put("KHR_materials_unlit", new UnlitExtensionLoader()); - defaultExtensionLoaders.put("KHR_texture_transform", new TextureTransformExtensionLoader()); + defaultExtensionLoaders.put("KHR_texture_transform", new TextureTransformExtensionLoader()); } void init(GltfLoader gltfLoader) { 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 b16f042f505..11e934666e0 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 @@ -65,7 +65,7 @@ public class GltfLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(GltfLoader.class.getName()); // Data cache for already parsed JME objects - private Map dataCache = new HashMap<>(); + private final Map dataCache = new HashMap<>(); private JsonArray scenes; private JsonArray nodes; private JsonArray meshes; @@ -85,12 +85,12 @@ public class GltfLoader implements AssetLoader { private JsonObject docRoot; private Node rootNode; - private FloatArrayPopulator floatArrayPopulator = new FloatArrayPopulator(); - private Vector3fArrayPopulator vector3fArrayPopulator = new Vector3fArrayPopulator(); - private QuaternionArrayPopulator quaternionArrayPopulator = new QuaternionArrayPopulator(); - private Matrix4fArrayPopulator matrix4fArrayPopulator = new Matrix4fArrayPopulator(); - private Map defaultMaterialAdapters = new HashMap<>(); - private CustomContentManager customContentManager = new CustomContentManager(); + private final FloatArrayPopulator floatArrayPopulator = new FloatArrayPopulator(); + private final Vector3fArrayPopulator vector3fArrayPopulator = new Vector3fArrayPopulator(); + private final QuaternionArrayPopulator quaternionArrayPopulator = new QuaternionArrayPopulator(); + private final Matrix4fArrayPopulator matrix4fArrayPopulator = new Matrix4fArrayPopulator(); + private final Map defaultMaterialAdapters = new HashMap<>(); + private final CustomContentManager customContentManager = new CustomContentManager(); private boolean useNormalsFlag = false; Map> skinnedSpatials = new HashMap<>(); From 7f927f0cfc46558a7f614973abe2b11101c81344 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 28 Dec 2022 09:06:28 +0330 Subject: [PATCH 057/140] Fix #1883 (Image class wrongly setting GL mips flags inside the constructor) (#1885) --- .../src/main/java/com/jme3/texture/Image.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 69fcb861185..b928fd9959d 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-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -807,11 +807,13 @@ public Image(Format format, int width, int height, ByteBuffer data, this(); - if (mipMapSizes != null && mipMapSizes.length <= 1) { - mipMapSizes = null; - } else { - needGeneratedMips = false; - mipsWereGenerated = true; + if (mipMapSizes != null) { + if (mipMapSizes.length <= 1) { + mipMapSizes = null; + } else { + needGeneratedMips = false; + mipsWereGenerated = true; + } } setFormat(format); From 5b9fc87234688bd74061a5fb134fdc2a6f526850 Mon Sep 17 00:00:00 2001 From: pavl_g <60224159+Scrappers-glitch@users.noreply.github.com> Date: Sun, 1 Jan 2023 07:12:01 +0200 Subject: [PATCH 058/140] AreaUtils: Migrated package to `com.jme3.util` (#1826) * AreaUtils: Migrated package to `com.jme3.util` * com.jme3.uitl.AreaUtils: removed code duplicates - added `final` class specifier * com.jme3.util.AreaUtils: fixed duplication build error * utils/AreaUtils.java: full migration to the utility package * scene/control/AreaUtils.java: removed utility methods - delegated functionality to `jme3.utils.AreaUtils` * scene/AreaUtils: fixed `jme3.util` package linking typo --- .../com/jme3/scene/control/AreaUtils.java | 54 ++++-------- .../main/java/com/jme3/util/AreaUtils.java | 88 +++++++++++++++++++ 2 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/util/AreaUtils.java 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 e5a7b4c98cb..52f388c7df0 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,8 +41,9 @@ * functions are very loose approximations. * @author Joshua Slack * @version $Id: AreaUtils.java 4131 2009-03-19 20:15:28Z blaine.dev $ + * @deprecated use {@link com.jme3.util.AreaUtils} instead, due to wrong package */ - +@Deprecated public class AreaUtils { /** * A private constructor to inhibit instantiation of this class. @@ -50,39 +51,16 @@ public class AreaUtils { private AreaUtils() { } - /** - * Estimate the screen area of a bounding volume. If the volume isn't a - * BoundingSphere, BoundingBox, or OrientedBoundingBox, 0 is returned. - * - * @param bound The bounds to calculate the volume from. - * @param distance The distance from camera to object. - * @param screenWidth The width of the screen. - * @return The area in pixels on the screen of the bounding volume. - */ - public static float calcScreenArea(BoundingVolume bound, float distance, float screenWidth) { - if (bound.getType() == BoundingVolume.Type.Sphere){ - return calcScreenArea((BoundingSphere) bound, distance, screenWidth); - }else if (bound.getType() == BoundingVolume.Type.AABB){ - return calcScreenArea((BoundingBox) bound, distance, screenWidth); - } - return 0.0f; - } - - private static float calcScreenArea(BoundingSphere bound, float distance, float screenWidth) { - // Where is the center point and a radius point that lies in a plan parallel to the view plane? -// // Calc radius based on these two points and plug into circle area formula. -// Vector2f centerSP = null; -// Vector2f outerSP = null; -// float radiusSq = centerSP.subtract(outerSP).lengthSquared(); - float radius = (bound.getRadius() * screenWidth) / (distance * 2); - return radius * radius * FastMath.PI; - } - - private static float calcScreenArea(BoundingBox bound, float distance, float screenWidth) { - // Calc as if we are a BoundingSphere for now... - float radiusSquare = bound.getXExtent() * bound.getXExtent() - + bound.getYExtent() * bound.getYExtent() - + bound.getZExtent() * bound.getZExtent(); - return ((radiusSquare * screenWidth * screenWidth) / (distance * distance * 4)) * FastMath.PI; - } -} \ No newline at end of file + /** + * Estimate the screen area of a bounding volume. If the volume isn't a + * BoundingSphere, BoundingBox, or OrientedBoundingBox, 0 is returned. + * + * @param bound The bounds to calculate the volume from. + * @param distance The distance from camera to object. + * @param screenWidth The width of the screen. + * @return The area in pixels on the screen of the bounding volume. + */ + public static float calcScreenArea(BoundingVolume bound, float distance, float screenWidth) { + return com.jme3.util.AreaUtils.calcScreenArea(bound, distance, screenWidth); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/AreaUtils.java b/jme3-core/src/main/java/com/jme3/util/AreaUtils.java new file mode 100644 index 00000000000..ce48dc763c3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/AreaUtils.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.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 + * functions are very loose approximations. + * + * @author Joshua Slack + * @version $Id: AreaUtils.java 4131 2009-03-19 20:15:28Z blaine.dev $ + */ +public final class AreaUtils { + /** + * A private constructor to inhibit instantiation of this class. + */ + private AreaUtils() { + } + + /** + * Estimate the screen area of a bounding volume. If the volume isn't a + * BoundingSphere, BoundingBox, or OrientedBoundingBox, 0 is returned. + * + * @param bound The bounds to calculate the volume from. + * @param distance The distance from camera to object. + * @param screenWidth The width of the screen. + * @return The area in pixels on the screen of the bounding volume. + */ + public static float calcScreenArea(BoundingVolume bound, float distance, float screenWidth) { + if (bound.getType() == BoundingVolume.Type.Sphere) { + return calcScreenArea((BoundingSphere) bound, distance, screenWidth); + } else if (bound.getType() == BoundingVolume.Type.AABB) { + return calcScreenArea((BoundingBox) bound, distance, screenWidth); + } + return 0.0f; + } + + private static float calcScreenArea(BoundingSphere bound, float distance, float screenWidth) { + // Where is the center point and a radius point that lies in a plan parallel to the view plane? + // // Calc radius based on these two points and plug into circle area formula. + // Vector2f centerSP = null; + // Vector2f outerSP = null; + // float radiusSq = centerSP.subtract(outerSP).lengthSquared(); + float radius = (bound.getRadius() * screenWidth) / (distance * 2); + return radius * radius * FastMath.PI; + } + + private static float calcScreenArea(BoundingBox bound, float distance, float screenWidth) { + // Calc as if we are a BoundingSphere for now... + float radiusSquare = bound.getXExtent() * bound.getXExtent() + + bound.getYExtent() * bound.getYExtent() + + bound.getZExtent() * bound.getZExtent(); + return ((radiusSquare * screenWidth * screenWidth) / (distance * distance * 4)) * FastMath.PI; + } +} From 5b948dff0d107374d89a78ccd6e35bf458456612 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 6 Jan 2023 22:41:45 -0800 Subject: [PATCH 059/140] Renderer: javadoc correction --- jme3-core/src/main/java/com/jme3/renderer/Renderer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index d25e5147817..753bf2f1a6e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -50,7 +50,6 @@ /** * Responsible for taking rendering commands and * executing them on the underlying video hardware. - * executes them on the underlying video hardware. * * @author Kirill Vainer */ From 0663306efc9941849fdfe1d4adba151392d195e1 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 6 Jan 2023 22:46:38 -0800 Subject: [PATCH 060/140] update the groovy-test library to v3.0.14 --- common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.gradle b/common.gradle index 6d38c283c31..308b86f3c7d 100644 --- a/common.gradle +++ b/common.gradle @@ -37,7 +37,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.13' + testImplementation 'org.codehaus.groovy:groovy-test:3.0.14' } From 00a859709032c192f3b48f5c596124cbaf883aca Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 6 Jan 2023 22:59:46 -0800 Subject: [PATCH 061/140] LICENSE.md: add 2023 to copyright years --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index ac2b55fb2a0..0d6db16108a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2009-2022 jMonkeyEngine. +Copyright (c) 2009-2023 jMonkeyEngine. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions From fe851dc8c058ad7a6e90bfa3c23774ea30cf65d6 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Mon, 9 Jan 2023 19:41:46 +0330 Subject: [PATCH 062/140] Fix #1892 (TestChooser does not show classes list when run with java 8) (#1893) * TestChooser:fix class list not showing when run with java 8. * Update copyright date. --- jme3-examples/src/main/java/jme3test/TestChooser.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index d8dafae5a6f..28cf10064d7 100644 --- a/jme3-examples/src/main/java/jme3test/TestChooser.java +++ b/jme3-examples/src/main/java/jme3test/TestChooser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -217,7 +217,13 @@ private void addAllFilesInDirectory(final Path directory, // we are only interested in .class files if (Files.isDirectory(file)) { if (recursive) { - addAllFilesInDirectory(file, allClasses, packageName + file.getFileName() + ".", true); + String dirName = String.valueOf(file.getFileName()); + if (dirName.endsWith("/")) { + // Seems java 8 adds "/" at the end of directory name when + // reading from jar filesystem. We need to remove it. - Ali-RS 2023-1-5 + dirName = dirName.substring(0, dirName.length() - 1); + } + addAllFilesInDirectory(file, allClasses, packageName + dirName + ".", true); } } else { Class result = load(packageName + file.getFileName()); From eda8b6518c917dcb9302669e2dda225008908cb9 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Tue, 10 Jan 2023 12:49:10 +0330 Subject: [PATCH 063/140] main.yml: use "temurin" openjdk. Fix #1896 (#1897) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3b3f3d97f0..360e39a8ced 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -112,7 +112,7 @@ jobs: - name: Setup the java environment uses: actions/setup-java@v3 with: - distribution: 'zulu' + distribution: 'temurin' java-version: ${{ matrix.jdk }} - name: Download natives for android From b0bd1a51ab5dd29f9a6df92a7c72f0cbb57f22b1 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 10 Jan 2023 09:36:26 -0800 Subject: [PATCH 064/140] README.md: add Demon Lord to the list of published games --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b6f8bf12b6c..7c26bd10033 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The engine is used by several commercial game studios and computer-science cours - [Cubic Nightmare](https://jaredbgreat.itch.io/cubic-nightmare) - [Chatter Games](https://chatter-games.com) - [Exotic Matter](https://exoticmatter.io) + - [Demon Lord](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1) ## Getting started From 36f9c895a28b9ff730bab55c252a4be5cdb2f2a2 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Tue, 10 Jan 2023 21:25:18 +0330 Subject: [PATCH 065/140] Fix #1773 (Wrong particle position when `worldSpace` flag equals to true) (#1889) * Add test case for issue #1773. * Fix wrong particle position when using 'EmitterMeshVertexShape' or 'EmitterMeshFaceShape' and worldSpace flag equal to true. The old code was interpolating particles position toward emitter world position and this was only working fine for EmitterPointShape and in the other shapes this was causing particles not keep the shape because they were being dragged toward emitter position. The new code calculates the distance vector from emitter last location to the current emitter location and subtracts it from particles position to generate a hypothetical position that is used for interpolation. * Add javadoc to TestIssue1773. * Minor javadoc fix. --- .../java/com/jme3/effect/ParticleEmitter.java | 13 +- .../java/jme3test/effect/TestIssue1773.java | 303 ++++++++++++++++++ 2 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 jme3-examples/src/main/java/jme3test/effect/TestIssue1773.java diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 0cbfca11e0b..9dba41eb16f 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -1081,6 +1081,12 @@ private void updateParticleState(float tpf) { } } + // Emitter distance from last location + Vector3f lastDistance = null; + if (lastPos != null && isInWorldSpace()) { + lastDistance = getWorldTranslation().subtract(lastPos, lastPos); + } + // Spawns particles within the tpf timeslot with proper age float interval = 1f / particlesPerSec; float originalTpf = tpf; @@ -1091,6 +1097,11 @@ private void updateParticleState(float tpf) { if (p != null) { p.life -= tpf; if (lastPos != null && isInWorldSpace()) { + // Generate a hypothetical position by subtracting distance + // vector from particle position and use for interpolating + // particle. This will fix discrete particles motion when + // emitter is moving fast. - Ali-RS 2023-1-2 + Vector3f lastPos = p.position.subtract(lastDistance, temp); p.position.interpolateLocal(lastPos, 1 - tpf / originalTpf); } if (p.life <= 0) { diff --git a/jme3-examples/src/main/java/jme3test/effect/TestIssue1773.java b/jme3-examples/src/main/java/jme3test/effect/TestIssue1773.java new file mode 100644 index 00000000000..b466815dfbe --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestIssue1773.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2009-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 jme3test.effect; + +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterMeshVertexShape; +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.input.controls.Trigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.FXAAFilter; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.CenterQuad; +import com.jme3.scene.shape.Torus; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import java.util.Arrays; + +/** + * Test case for Issue 1773 (Wrong particle position when using + * 'EmitterMeshVertexShape' or 'EmitterMeshFaceShape' and worldSpace + * flag equal to true) + * + * If the test succeeds, the particles will be generated from the vertices + * (for EmitterMeshVertexShape) or from the faces (for EmitterMeshFaceShape) + * of the torus mesh. If the test fails, the particles will appear in the + * center of the torus when worldSpace flag is set to true. + * + * @author capdevon + */ +public class TestIssue1773 extends SimpleApplication implements ActionListener { + + public static void main(String[] args) { + TestIssue1773 app = new TestIssue1773(); + AppSettings settings = new AppSettings(true); + settings.setResolution(1280, 720); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + app.setSettings(settings); + app.setPauseOnLostFocus(false); + app.setShowSettings(false); + app.start(); + } + + private ParticleEmitter emit; + private Node myModel; + private BitmapText emitUI; + private MotionEvent motionControl; + private boolean playing; + + @Override + public void simpleInitApp() { + + BitmapText hud = createTextUI(ColorRGBA.White, 20, 15); + hud.setText("Play/Pause Motion: KEY_SPACE, InWorldSpace: KEY_I"); + + emitUI = createTextUI(ColorRGBA.Blue, 20, 15 * 2); + + configCamera(); + setupLights(); + setupGround(); + setupCircle(); + createMotionControl(); + setupKeys(); + } + + /** + * Crates particle emitter and adds it to root node. + */ + private void setupCircle() { + myModel = new Node("FieryCircle"); + + Geometry torus = createTorus(1f); + myModel.attachChild(torus); + + emit = createParticleEmitter(torus, true); + myModel.attachChild(emit); + + rootNode.attachChild(myModel); + } + + /** + * Creates torus geometry used for the emitter shape. + */ + private Geometry createTorus(float radius) { + float s = radius / 8f; + Geometry geo = new Geometry("CircleXZ", new Torus(64, 4, s, radius)); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + mat.getAdditionalRenderState().setWireframe(true); + geo.setMaterial(mat); + return geo; + } + + /** + * Creates a particle emitter that will emit the particles from + * the given shape's vertices. + */ + private ParticleEmitter createParticleEmitter(Geometry geo, boolean pointSprite) { + Type type = pointSprite ? Type.Point : Type.Triangle; + ParticleEmitter emitter = new ParticleEmitter("Emitter", type, 1000); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png")); + mat.setBoolean("PointSprite", pointSprite); + emitter.setMaterial(mat); + emitter.setLowLife(1); + emitter.setHighLife(1); + emitter.setImagesX(15); + emitter.setStartSize(0.04f); + emitter.setEndSize(0.02f); + emitter.setStartColor(ColorRGBA.Orange); + emitter.setEndColor(ColorRGBA.Red); + emitter.setParticlesPerSec(900); + emitter.setGravity(0, 0f, 0); + //emitter.getParticleInfluencer().setVelocityVariation(1); + //emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0)); + emitter.setShape(new EmitterMeshVertexShape(Arrays.asList(geo.getMesh()))); + //emitter.setShape(new EmitterMeshFaceShape(Arrays.asList(geo.getMesh()))); + return emitter; + } + + /** + * Creates a motion control that will move particle emitter in + * a circular path. + */ + private void createMotionControl() { + + float radius = 5f; + float height = 1.10f; + + MotionPath path = new MotionPath(); + path.setCycle(true); + + for (int i = 0; i < 8; i++) { + float x = FastMath.sin(FastMath.QUARTER_PI * i) * radius; + float z = FastMath.cos(FastMath.QUARTER_PI * i) * radius; + path.addWayPoint(new Vector3f(x, height, z)); + } + //path.enableDebugShape(assetManager, rootNode); + + motionControl = new MotionEvent(myModel, path); + motionControl.setLoopMode(LoopMode.Loop); + //motionControl.setInitialDuration(15f); + //motionControl.setSpeed(2f); + motionControl.setDirectionType(MotionEvent.Direction.Path); + } + + /** + * Use keyboard space key to toggle emitter motion and I key to + * toggle inWorldSpace flag. By default, inWorldSpace flag is on + * and emitter motion is off. + */ + private void setupKeys() { + addMapping("ToggleMotionEvent", new KeyTrigger(KeyInput.KEY_SPACE)); + addMapping("InWorldSpace", new KeyTrigger(KeyInput.KEY_I)); + } + + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("InWorldSpace") && isPressed) { + boolean worldSpace = emit.isInWorldSpace(); + emit.setInWorldSpace(!worldSpace); + + } else if (name.equals("ToggleMotionEvent") && isPressed) { + if (playing) { + playing = false; + motionControl.pause(); + } else { + playing = true; + motionControl.play(); + } + } + } + + @Override + public void simpleUpdate(float tpf) { + emitUI.setText("InWorldSpace: " + emit.isInWorldSpace()); + } + + private void configCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(10); + + cam.setLocation(new Vector3f(0, 6f, 9.2f)); + cam.lookAt(Vector3f.UNIT_Y, Vector3f.UNIT_Y); + + float aspect = (float) cam.getWidth() / cam.getHeight(); + cam.setFrustumPerspective(45, aspect, 0.1f, 1000f); + } + + /** + * Adds a ground to the scene + */ + private void setupGround() { + CenterQuad quad = new CenterQuad(12, 12); + quad.scaleTextureCoordinates(new Vector2f(2, 2)); + Geometry floor = new Geometry("Floor", quad); + Material mat = new Material(assetManager, Materials.LIGHTING); + Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + tex.setWrap(Texture.WrapMode.Repeat); + mat.setTexture("DiffuseMap", tex); + floor.setMaterial(mat); + floor.rotate(-FastMath.HALF_PI, 0, 0); + rootNode.attachChild(floor); + } + + /** + * Adds lights and filters + */ + private void setupLights() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + AmbientLight ambient = new AmbientLight(); + ambient.setColor(ColorRGBA.White); + //rootNode.addLight(ambient); + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 4096, 3); + dlsf.setLight(sun); + dlsf.setShadowIntensity(0.4f); + dlsf.setShadowZExtend(256); + + FXAAFilter fxaa = new FXAAFilter(); + BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(bloom); + fpp.addFilter(dlsf); + fpp.addFilter(fxaa); + viewPort.addProcessor(fpp); + } + + /** + * Creates a bitmap test used for displaying debug info. + */ + private BitmapText createTextUI(ColorRGBA color, float xPos, float yPos) { + BitmapFont font = assetManager.loadFont("Interface/Fonts/Console.fnt"); + BitmapText bmp = new BitmapText(font); + bmp.setSize(font.getCharSet().getRenderedSize()); + bmp.setLocalTranslation(xPos, settings.getHeight() - yPos, 0); + bmp.setColor(color); + guiNode.attachChild(bmp); + return bmp; + } +} From d07f66d15bcf89a574e0f744a4f3a5502e89afe7 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 10 Jan 2023 21:51:12 -0800 Subject: [PATCH 066/140] jme3-niftygui: solve issue #1891 (incorrect fullscreen layout) (#1895) --- .../main/java/com/jme3/niftygui/NiftyJmeDisplay.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java index 714c67b9281..10605ed08a9 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.input.event.KeyInputEvent; import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; +import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; @@ -321,7 +322,12 @@ public void initialize(RenderManager rm, ViewPort vp) { this.renderer = rm.getRenderer(); inputSys.reset(); - inputSys.setHeight(vp.getCamera().getHeight()); + + // window size may have changed since the private initialize() above + Camera camera = vp.getCamera(); + this.w = camera.getWidth(); + this.h = camera.getHeight(); + inputSys.setHeight(h); } public Nifty getNifty() { From 3e1b823bdb7c7a2e60916dab32186141b3fd9800 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 11 Jan 2023 09:23:56 +0330 Subject: [PATCH 067/140] jme3-lwjgl:updated to lwjgl v2.9.4 hosted by org.jmonkeyengine. Fix #1247, #1215, #947 (#1902) --- jme3-lwjgl/build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jme3-lwjgl/build.gradle b/jme3-lwjgl/build.gradle index 6bfe60863af..f61ef7b06c5 100644 --- a/jme3-lwjgl/build.gradle +++ b/jme3-lwjgl/build.gradle @@ -1,7 +1,10 @@ dependencies { api project(':jme3-core') api project(':jme3-desktop') - api 'org.lwjgl.lwjgl:lwjgl:2.9.3' + + api 'org.jmonkeyengine:lwjgl:2.9.4' + runtimeOnly 'org.jmonkeyengine:lwjgl-platform:2.9.4' + /* * 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 From 4e80c9bc6891ee3c0ca54e707c165bb3a332fe41 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 11 Jan 2023 09:28:07 +0330 Subject: [PATCH 068/140] Fix #1890 (crashes attempting to run example apps in fullscreen with LWJGL v2) (#1898) * jme3-lwjgl:fallback to standard 60Hz fullscreen display mode if the specified frequency in the AppSettings is not available and log a warning. * Fallback to whatever bps or frequency is available. Added a wild-card value to let selecting any bps or frequency available by passing -1. * Support AWT display frequency model. Looks like AWT uses mathematics round to convert float frequency values to int while lwjgl 2 uses mathematics floor. For example if frequency is 59.83, AWT will return 60 but lwjgl 2 will return 59. * Remove redundant check. * Added documentation for getFullscreenDisplayMode method. --- .../com/jme3/system/lwjgl/LwjglDisplay.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index cd00e1c3d54..d5d46ad6ca2 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,9 @@ import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,14 +53,28 @@ public class LwjglDisplay extends LwjglAbstractDisplay { private final AtomicBoolean needRestart = new AtomicBoolean(false); private PixelFormat pixelFormat; + /** + * @param width The required display width + * @param height The required display height + * @param bpp The required bits per pixel. If -1 is passed it will return + * whatever bpp is found + * @param freq The required frequency, if -1 is passed it will return + * whatever frequency is found + * @return The {@link DisplayMode} matches with specified settings or + * return null if no matching display mode is found + */ protected DisplayMode getFullscreenDisplayMode(int width, int height, int bpp, int freq){ try { DisplayMode[] modes = Display.getAvailableDisplayModes(); for (DisplayMode mode : modes) { if (mode.getWidth() == width && mode.getHeight() == height - && (mode.getBitsPerPixel() == bpp || (bpp == 24 && mode.getBitsPerPixel() == 32)) - && (mode.getFrequency() == freq || (freq == 60 && mode.getFrequency() == 59))) { + && (mode.getBitsPerPixel() == bpp || (bpp == 24 && mode.getBitsPerPixel() == 32) || bpp == -1) + // Looks like AWT uses mathematical round to convert floating point + // frequency values to int while lwjgl 2 uses mathematical floor. + // For example if frequency is 59.83, AWT will return 60 but lwjgl2 + // will return 59. This is what I observed on Linux. - Ali-RS 2023-1-10 + && (Math.abs(mode.getFrequency() - freq) <= 1 || freq == -1)) { return mode; } } @@ -70,16 +87,22 @@ protected DisplayMode getFullscreenDisplayMode(int width, int height, int bpp, i @Override protected void createContext(AppSettings settings) throws LWJGLException{ DisplayMode displayMode; - if (settings.getWidth() <= 0 || settings.getHeight() <= 0){ + if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { displayMode = Display.getDesktopDisplayMode(); settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); - }else if (settings.isFullscreen()){ + } else if (settings.isFullscreen()) { displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), - settings.getBitsPerPixel(), settings.getFrequency()); + settings.getBitsPerPixel(), settings.getFrequency()); if (displayMode == null) { - throw new RuntimeException("Unable to find fullscreen display mode matching settings"); + // Fall back to whatever mode is available at the specified width & height + displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), -1, -1); + if (displayMode == null) { + throw new RuntimeException("Unable to find fullscreen display mode matching settings"); + } else { + logger.log(Level.WARNING, "Unable to find fullscreen display mode matching settings, falling back to: {0}", displayMode); + } } - }else{ + } else { displayMode = new DisplayMode(settings.getWidth(), settings.getHeight()); } From c739b66abeaca9f6a9abf1daddc81992983d8767 Mon Sep 17 00:00:00 2001 From: richardTingle <6330028+richardTingle@users.noreply.github.com> Date: Wed, 11 Jan 2023 06:10:35 +0000 Subject: [PATCH 069/140] When in VR attach the debug scene to the two eye's scenes. Fix#1795 (#1888) * #1795 When in VR attach the debug scene to the two eye's scenes This ensures they show up correctly * #1795 Whitespace corrections * #1795 Further whitespace corrections * #1795 Yet more whitespace corrections * #1795 Add explanatory comment as to why VR and non-VR have totally different approaches --- jme3-jbullet/build.gradle | 1 + .../bullet/debug/BulletDebugAppState.java | 46 +++++++++++++++++-- .../main/java/com/jme3/app/VRAppState.java | 3 +- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/jme3-jbullet/build.gradle b/jme3-jbullet/build.gradle index 39bb1203683..1a2621a4208 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -18,6 +18,7 @@ dependencies { api 'javax.vecmath:vecmath:1.5.2' api project(':jme3-core') api project(':jme3-terrain') + compileOnly project(':jme3-vr') //is selectively used if on classpath testRuntimeOnly project(':jme3-desktop') testRuntimeOnly project(':jme3-testdata') } diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java index 3ee27e1eeaa..b8df7695ff6 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java @@ -32,6 +32,7 @@ package com.jme3.bullet.debug; import com.jme3.app.Application; +import com.jme3.app.VRAppState; import com.jme3.app.state.AbstractAppState; import com.jme3.app.state.AppStateManager; import com.jme3.asset.AssetManager; @@ -67,6 +68,12 @@ public class BulletDebugAppState extends AbstractAppState { * message logger for this class */ protected static final Logger logger = Logger.getLogger(BulletDebugAppState.class.getName()); + + /** + * caches the virtual reality state (null means not yet determined) + */ + private Boolean isVr = null; + /** * limit which objects are visualized, or null to visualize all objects */ @@ -165,9 +172,18 @@ public void initialize(AppStateManager stateManager, Application app) { this.assetManager = app.getAssetManager(); setupMaterials(app); physicsDebugRootNode.setCullHint(Spatial.CullHint.Never); - viewPort = rm.createMainView("Physics Debug Overlay", app.getCamera()); - viewPort.setClearFlags(false, true, false); - viewPort.attachScene(physicsDebugRootNode); + + if (isVr()) { + /* This is a less good solution than the non-vr version (as the debug shapes can be obscured by the regular + * geometry), however it is the best possible as VR does not currently support multiple viewports per eye */ + VRAppState vrAppState = stateManager.getState(VRAppState.ID, VRAppState.class); + vrAppState.getLeftViewPort().attachScene(physicsDebugRootNode); + vrAppState.getRightViewPort().attachScene(physicsDebugRootNode); + } else { + viewPort = rm.createMainView("Physics Debug Overlay", app.getCamera()); + viewPort.setClearFlags(false, true, false); + viewPort.attachScene(physicsDebugRootNode); + } } /** @@ -178,7 +194,14 @@ public void initialize(AppStateManager stateManager, Application app) { */ @Override public void cleanup() { - rm.removeMainView(viewPort); + if (isVr()) { + VRAppState vrAppState = app.getStateManager().getState(VRAppState.ID, VRAppState.class); + vrAppState.getLeftViewPort().detachScene(physicsDebugRootNode); + vrAppState.getRightViewPort().detachScene(physicsDebugRootNode); + } else { + rm.removeMainView(viewPort); + } + super.cleanup(); } @@ -413,4 +436,17 @@ public static interface DebugAppStateFilter { */ public boolean displayObject(Object obj); } -} + + private boolean isVr() { + if (isVr == null) { + try { + VRAppState vrAppState = app.getStateManager().getState(VRAppState.ID, VRAppState.class); + isVr = vrAppState != null && !vrAppState.DISABLE_VR; + } catch (NoClassDefFoundError e) { + //Vr isn't even on the classpath + isVr = false; + } + } + return isVr; + } +} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/app/VRAppState.java b/jme3-vr/src/main/java/com/jme3/app/VRAppState.java index cbbde4005fd..fa0956b2b26 100644 --- a/jme3-vr/src/main/java/com/jme3/app/VRAppState.java +++ b/jme3-vr/src/main/java/com/jme3/app/VRAppState.java @@ -74,6 +74,7 @@ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr */ public class VRAppState extends AbstractAppState { + public static final String ID = "VRAppState"; private static final Logger logger = Logger.getLogger(VRAppState.class.getName()); /** @@ -105,7 +106,7 @@ public class VRAppState extends AbstractAppState { * @param environment the {@link VREnvironment VR environment} that this app state is using. */ public VRAppState(VREnvironment environment) { - super(); + super(ID); this.environment = environment; From 0d6d6d9568b2664bf40ffdb7fa267fa0e7d765e4 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Wed, 11 Jan 2023 01:14:44 -0500 Subject: [PATCH 070/140] Refactored PBR Terrain to use new for-loops. Fix #1785 (#1901) Drastically reduced code by utilizing JME's new potential for define-compatible for-loops in shaders. Also cleaned up some other little things like indentations, comments, and code placement so that the code is much more organized and easier to understand. --- .../Common/MatDefs/Terrain/PBRTerrain.frag | 852 +++++------------- 1 file changed, 202 insertions(+), 650 deletions(-) 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 ac91e8eb2e5..4832bef04c7 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag @@ -2,19 +2,28 @@ #import "Common/ShaderLib/PBR.glsllib" #import "Common/ShaderLib/Parallax.glsllib" #import "Common/ShaderLib/Lighting.glsllib" +#import "MatDefs/ShaderLib/AfflictionLib.glsllib" -#import "Common/MatDefs/Terrain/AfflictionLib.glsllib" +varying vec3 wPosition; +varying vec3 vNormal; +varying vec2 texCoord; +uniform vec3 g_CameraPosition; +varying vec3 vPosition; +varying vec3 vnPosition; +varying vec3 vViewDir; +varying vec4 vLightDir; +varying vec4 vnLightDir; +varying vec3 lightVec; +varying vec3 inNormal; +varying vec3 wNormal; #ifdef DEBUG_VALUES_MODE - uniform int m_DebugValuesMode; + uniform int m_DebugValuesMode; #endif - uniform vec4 g_LightData[NB_LIGHTS]; uniform vec4 g_AmbientLightColor; -varying vec3 wPosition; - #if NB_PROBES >= 1 uniform samplerCube g_PrefEnvMap; uniform vec3 g_ShCoeffs[9]; @@ -31,17 +40,43 @@ varying vec3 wPosition; uniform mat4 g_LightProbeData3; #endif +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; +#endif -varying vec3 vNormal; -varying vec2 texCoord; - -uniform vec3 g_CameraPosition; - - -vec3 norm; +//texture-slot params for 12 unique texture slots (0-11) : +#for i=0..12 ( $0 ) + uniform int m_AfflictionMode_$i; + uniform float m_Roughness_$i; + uniform float m_Metallic_$i; + + #ifdef ALBEDOMAP_$i + uniform sampler2D m_AlbedoMap_$i; + #endif + #ifdef ALBEDOMAP_$i_SCALE + uniform float m_AlbedoMap_$i_scale; + #endif + #ifdef NORMALMAP_$i + uniform sampler2D m_NormalMap_$i; + #endif +#endfor +//3 alpha maps : +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif +#ifdef ALPHAMAP_1 + uniform sampler2D m_AlphaMap_1; +#endif +#ifdef ALPHAMAP_2 + uniform sampler2D m_AlphaMap_2; +#endif +#ifdef DISCARD_ALPHA + uniform float m_AlphaDiscardThreshold; +#endif +//fog vars for basic fog : #ifdef USE_FOG #import "Common/ShaderLib/MaterialFog.glsllib" uniform vec4 m_FogColor; @@ -49,641 +84,116 @@ vec3 norm; uniform vec2 m_LinearFog; #endif - #ifdef FOG_EXP uniform float m_ExpFog; #endif - #ifdef FOG_EXPSQ uniform float m_ExpSqFog; #endif +//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the +// vertex colors, or it can be set as a static value for an entire material with the StaticSunIntensity float param +#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) + varying vec4 vertColors; +#endif -varying vec3 vPosition; -varying vec3 vnPosition; -varying vec3 vViewDir; -varying vec4 vLightDir; -varying vec4 vnLightDir; -varying vec3 lightVec; -varying vec3 inNormal; +#ifdef STATIC_SUN_INTENSITY + uniform float m_StaticSunIntensity; +#endif +//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the +//sun is more/less bright than the brightest point light for each fragment to determine how the light probe's ambient light should be scaled later on in light calculation code +float brightestPointLight = 0.0; -uniform int m_AfflictionMode_0; -uniform int m_AfflictionMode_1; -uniform int m_AfflictionMode_2; -uniform int m_AfflictionMode_3; -uniform int m_AfflictionMode_4; -uniform int m_AfflictionMode_5; -uniform int m_AfflictionMode_6; -uniform int m_AfflictionMode_7; -uniform int m_AfflictionMode_8; -uniform int m_AfflictionMode_9; -uniform int m_AfflictionMode_10; -uniform int m_AfflictionMode_11; - -uniform float m_Roughness_0; -uniform float m_Roughness_1; -uniform float m_Roughness_2; -uniform float m_Roughness_3; -uniform float m_Roughness_4; -uniform float m_Roughness_5; -uniform float m_Roughness_6; -uniform float m_Roughness_7; -uniform float m_Roughness_8; -uniform float m_Roughness_9; -uniform float m_Roughness_10; -uniform float m_Roughness_11; - -uniform float m_Metallic_0; -uniform float m_Metallic_1; -uniform float m_Metallic_2; -uniform float m_Metallic_3; -uniform float m_Metallic_4; -uniform float m_Metallic_5; -uniform float m_Metallic_6; -uniform float m_Metallic_7; -uniform float m_Metallic_8; -uniform float m_Metallic_9; -uniform float m_Metallic_10; -uniform float m_Metallic_11; - - -#ifdef AFFLICTIONTEXTURE +//optional affliction paramaters that use the AfflictionAlphaMap's green channel for splatting m_SplatAlbedoMap and the red channel for splatting desaturation : +#ifdef AFFLICTIONTEXTURE uniform sampler2D m_AfflictionAlphaMap; #endif - #ifdef USE_SPLAT_NOISE uniform float m_SplatNoiseVar; #endif - -//defined for sub terrains that arent equal to each map tile size when AfflictionAlphaMap is defined +//only defined for non-terrain geoemtries and terrains that are not positioned nor sized in correlation to the 2d array of AfflictionAlphaMaps used for splatting accross large tile based scenes in a grid #ifdef TILELOCATION uniform float m_TileWidth; uniform vec3 m_TileLocation; #endif - -uniform int m_AfflictionSplatScale; #ifdef AFFLICTIONALBEDOMAP uniform sampler2D m_SplatAlbedoMap; #endif - #ifdef AFFLICTIONNORMALMAP uniform sampler2D m_SplatNormalMap; #endif - #ifdef AFFLICTIONROUGHNESSMETALLICMAP uniform sampler2D m_SplatRoughnessMetallicMap; #endif - #ifdef AFFLICTIONEMISSIVEMAP uniform sampler2D m_SplatEmissiveMap; #endif +uniform int m_AfflictionSplatScale; uniform float m_AfflictionRoughnessValue; uniform float m_AfflictionMetallicValue; uniform float m_AfflictionEmissiveValue; uniform vec4 m_AfflictionEmissiveColor; - -#ifdef ALBEDOMAP_0 - uniform sampler2D m_AlbedoMap_0; -#endif -#ifdef ALBEDOMAP_1 - uniform sampler2D m_AlbedoMap_1; -#endif -#ifdef ALBEDOMAP_2 - uniform sampler2D m_AlbedoMap_2; -#endif -#ifdef ALBEDOMAP_3 - uniform sampler2D m_AlbedoMap_3; -#endif -#ifdef ALBEDOMAP_4 - uniform sampler2D m_AlbedoMap_4; -#endif -#ifdef ALBEDOMAP_5 - uniform sampler2D m_AlbedoMap_5; -#endif -#ifdef ALBEDOMAP_6 - uniform sampler2D m_AlbedoMap_6; -#endif -#ifdef ALBEDOMAP_7 - uniform sampler2D m_AlbedoMap_7; -#endif -#ifdef ALBEDOMAP_8 - uniform sampler2D m_AlbedoMap_8; -#endif -#ifdef ALBEDOMAP_9 - uniform sampler2D m_AlbedoMap_9; -#endif -#ifdef ALBEDOMAP_10 - uniform sampler2D m_AlbedoMap_10; -#endif -#ifdef ALBEDOMAP_11 - uniform sampler2D m_AlbedoMap_11; -#endif - - -#ifdef ALBEDOMAP_0_SCALE - uniform float m_AlbedoMap_0_scale; -#endif -#ifdef ALBEDOMAP_1_SCALE - uniform float m_AlbedoMap_1_scale; -#endif -#ifdef ALBEDOMAP_2_SCALE - uniform float m_AlbedoMap_2_scale; -#endif -#ifdef ALBEDOMAP_3_SCALE - uniform float m_AlbedoMap_3_scale; -#endif -#ifdef ALBEDOMAP_4_SCALE - uniform float m_AlbedoMap_4_scale; -#endif -#ifdef ALBEDOMAP_5_SCALE - uniform float m_AlbedoMap_5_scale; -#endif -#ifdef ALBEDOMAP_6_SCALE - uniform float m_AlbedoMap_6_scale; -#endif -#ifdef ALBEDOMAP_7_SCALE - uniform float m_AlbedoMap_7_scale; -#endif -#ifdef ALBEDOMAP_8_SCALE - uniform float m_AlbedoMap_8_scale; -#endif -#ifdef ALBEDOMAP_9_SCALE - uniform float m_AlbedoMap_9_scale; -#endif -#ifdef ALBEDOMAP_10_SCALE - uniform float m_AlbedoMap_10_scale; -#endif -#ifdef ALBEDOMAP_11_SCALE - uniform float m_AlbedoMap_11_scale; -#endif - - -#ifdef ALPHAMAP - uniform sampler2D m_AlphaMap; -#endif -#ifdef ALPHAMAP_1 - uniform sampler2D m_AlphaMap_1; -#endif -#ifdef ALPHAMAP_2 - uniform sampler2D m_AlphaMap_2; -#endif - -#ifdef NORMALMAP_0 - uniform sampler2D m_NormalMap_0; -#endif -#ifdef NORMALMAP_1 - uniform sampler2D m_NormalMap_1; -#endif -#ifdef NORMALMAP_2 - uniform sampler2D m_NormalMap_2; -#endif -#ifdef NORMALMAP_3 - uniform sampler2D m_NormalMap_3; -#endif -#ifdef NORMALMAP_4 - uniform sampler2D m_NormalMap_4; -#endif -#ifdef NORMALMAP_5 - uniform sampler2D m_NormalMap_5; -#endif -#ifdef NORMALMAP_6 - uniform sampler2D m_NormalMap_6; -#endif -#ifdef NORMALMAP_7 - uniform sampler2D m_NormalMap_7; -#endif -#ifdef NORMALMAP_8 - uniform sampler2D m_NormalMap_8; -#endif -#ifdef NORMALMAP_9 - uniform sampler2D m_NormalMap_9; -#endif -#ifdef NORMALMAP_10 - uniform sampler2D m_NormalMap_10; -#endif -#ifdef NORMALMAP_11 - uniform sampler2D m_NormalMap_11; -#endif - -#ifdef DISCARD_ALPHA - uniform float m_AlphaDiscardThreshold; -#endif - - - - vec4 afflictionVector; +float noiseHash; +float livelinessValue; +float afflictionValue; +int afflictionMode = 1; -varying vec3 wNormal; - -#ifdef TRI_PLANAR_MAPPING - varying vec4 wVertex; - -#endif - +//general temp vars : +vec4 tempAlbedo, tempNormal, tempEmissiveColor; +float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; vec3 viewDir; - vec2 coord; -vec4 albedo; +vec4 albedo = vec4(1.0); vec3 normal = vec3(0.5,0.5,1); -vec3 newNormal; +vec3 norm; float Metallic; float Roughness; float packedAoValue = 1.0; vec4 emissive; float emissiveIntensity = 1.0; - +float indoorSunLightExposure = 1.0; + vec4 packedMetallicRoughnessAoEiVec; vec4 packedNormalParallaxVec; -vec4 tempAlbedo, tempNormal, tempEmissiveColor; -float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; - -float noiseHash; -float livelinessValue; -float afflictionValue; -int afflictionMode = 1; - - - -#define DEFINE_COORD(index) vec2 coord##index = texCoord * m_AlbedoMap##index##_scale; - -#define BLEND(index, ab)\ - DEFINE_COORD(index)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo.rgb = texture2D(m_AlbedoMap##index, coord##index).rgb;\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo.rgb = mix( albedo.rgb, tempAlbedo.rgb ,ab );\ - normal.rgb = mix(normal.xyz, wNormal.rgb, ab);\ - Metallic = mix(Metallic, m_Metallic##index, ab);\ - Roughness = mix(Roughness, m_Roughness##index, ab); - - - - -#define BLEND_NORMAL(index, ab)\ - DEFINE_COORD(index)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo.rgb = texture2D(m_AlbedoMap##index, coord##index).rgb;\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo.rgb = mix( albedo.rgb, tempAlbedo.rgb ,ab );\ - Metallic = mix(Metallic, m_Metallic##index, ab);\ - Roughness = mix(Roughness, m_Roughness##index, ab);\ - tempNormal.xyz = texture2D(m_NormalMap##index, coord##index).xyz;\ - tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal);\ - normal.rgb = mix(normal.xyz, tempNormal.xyz, ab); - -//BLEND METHODS FOR TRIPLANAR... -#define TRI_BLEND(index, ab, worldCoords, blending)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo = getTriPlanarBlend(worldCoords, blending, m_AlbedoMap##index, m_AlbedoMap##index##_scale);\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo = mix( albedo, tempAlbedo ,ab );\ - normal.rgb = mix(normal.rgb, wNormal.rgb, ab);\ - Metallic = mix(Metallic, m_Metallic##index, ab);\ - Roughness = mix(Roughness, m_Roughness##index, ab); - - -#define TRI_BLEND_NORMAL(index, ab, worldCoords, blending)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo.rgb = getTriPlanarBlend(worldCoords, blending, m_AlbedoMap##index, m_AlbedoMap##index##_scale).rgb;\ - tempNormal.rgb = getTriPlanarBlend(worldCoords, blending, m_NormalMap##index, m_AlbedoMap##index##_scale).rgb;\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo.rgb = mix( albedo.rgb, tempAlbedo.rgb ,ab );\ - tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal);\ - normal.rgb = mix(normal.xyz, tempNormal.xyz, ab);\ - Metallic = mix(Metallic, m_Metallic##index, ab);\ - Roughness = mix(Roughness, m_Roughness##index, ab); - -#ifdef ALPHAMAP - -vec4 calculateAlbedoBlend(in vec2 texCoord) { - vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); - vec4 albedo = vec4(1.0); - - - - Roughness = m_Roughness_0; - Metallic = m_Metallic_0 ; - - vec3 blending = abs( wNormal ); - blending = (blending -0.2) * 0.7; - blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) - float b = (blending.x + blending.y + blending.z); - blending /= vec3(b, b, b); - - - #ifdef ALPHAMAP_1 - vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); - #endif - #ifdef ALPHAMAP_2 - vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); - #endif - #ifdef ALBEDOMAP_0 - //NOTE! the old (phong) terrain shaders do not have an "_0" for the first diffuse map, it is just "DiffuseMap" - #ifdef NORMALMAP_0 - BLEND_NORMAL(_0, alphaBlend.r) - #else - BLEND(_0, alphaBlend.r) - #endif - - #endif - #ifdef ALBEDOMAP_1 - #ifdef NORMALMAP_1 - BLEND_NORMAL(_1, alphaBlend.g) - #else - BLEND(_1, alphaBlend.g) - #endif - - #endif - #ifdef ALBEDOMAP_2 - #ifdef NORMALMAP_2 - BLEND_NORMAL(_2, alphaBlend.b) - #else - BLEND(_2, alphaBlend.b) - #endif - - #endif - #ifdef ALBEDOMAP_3 - #ifdef NORMALMAP_3 - BLEND_NORMAL(_3, alphaBlend.a) - #else - BLEND(_3, alphaBlend.a) - #endif - - #endif - - #ifdef ALPHAMAP_1 - #ifdef ALBEDOMAP_4 - #ifdef NORMALMAP_4 - BLEND_NORMAL(_4, alphaBlend1.r) - #else - BLEND(_4, alphaBlend1.r) - #endif - - #endif - #ifdef ALBEDOMAP_5 - #ifdef NORMALMAP_5 - BLEND_NORMAL(_5, alphaBlend1.g) - #else - BLEND(_5, alphaBlend1.g) - #endif - - #endif - #ifdef ALBEDOMAP_6 - #ifdef NORMALMAP_6 - BLEND_NORMAL(_6, alphaBlend1.b) - #else - BLEND(_6, alphaBlend1.b) - #endif - - #endif - #ifdef ALBEDOMAP_7 - #ifdef NORMALMAP_7 - BLEND_NORMAL(_7, alphaBlend1.a) - #else - BLEND(_7, alphaBlend1.a) - #endif - - #endif - #endif - - #ifdef ALPHAMAP_2 - #ifdef ALBEDOMAP_8 - #ifdef NORMALMAP_8 - BLEND_NORMAL(_8, alphaBlend2.r) - #else - BLEND(_8, alphaBlend2.r) - #endif - - #endif - #ifdef ALBEDOMAP_9 - #ifdef NORMALMAP_9 - BLEND_NORMAL(_9, alphaBlend2.g) - #else - BLEND(_9, alphaBlend2.g) - #endif - - #endif - #ifdef ALBEDOMAP_10 - #ifdef NORMALMAP_10 - BLEND_NORMAL(_10, alphaBlend2.b) - #else - BLEND(_10, alphaBlend2.b) - #endif - - #endif - #ifdef ALBEDOMAP_11 - #ifdef NORMALMAP_11 - BLEND_NORMAL(_11, alphaBlend2.a) - #else - BLEND(_11, alphaBlend2.a) - #endif - - #endif - #endif - - return albedo; - } - - - -// TRI PLANAR ALPHA MAP TEXTURES_ _ _ _ \/ - - #ifdef TRI_PLANAR_MAPPING - - - vec4 calculateTriPlanarAlbedoBlend(in vec3 wNorm, in vec4 wVert, in vec2 texCoord, vec3 blending) { - vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); - vec4 albedo = vec4(1.0); - - - - Roughness = m_Roughness_0; - Metallic = m_Metallic_0 ; - - - - - #ifdef ALPHAMAP_1 - vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); - #endif - #ifdef ALPHAMAP_2 - vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); - #endif - #ifdef ALBEDOMAP_0 - //NOTE! the old (phong) terrain shaders do not have an "_0" for the first diffuse map, it is just "DiffuseMap" - #ifdef NORMALMAP_0 - TRI_BLEND_NORMAL(_0, alphaBlend.r, wVertex, blending) - #else - TRI_BLEND(_0, alphaBlend.r, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_1 - #ifdef NORMALMAP_1 - TRI_BLEND_NORMAL(_1, alphaBlend.g, wVertex, blending) - #else - TRI_BLEND(_1, alphaBlend.g, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_2 - #ifdef NORMALMAP_2 - TRI_BLEND_NORMAL(_2, alphaBlend.b, wVertex, blending) - #else - TRI_BLEND(_2, alphaBlend.b, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_3 - #ifdef NORMALMAP_3 - TRI_BLEND_NORMAL(_3, alphaBlend.a, wVertex, blending) - #else - TRI_BLEND(_3, alphaBlend.a, wVertex, blending) - #endif - - #endif - - #ifdef ALPHAMAP_1 - #ifdef ALBEDOMAP_4 - #ifdef NORMALMAP_4 - TRI_BLEND_NORMAL(_4, alphaBlend1.r, wVertex, blending) - #else - TRI_BLEND(_4, alphaBlend1.r, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_5 - #ifdef NORMALMAP_5 - TRI_BLEND_NORMAL(_5, alphaBlend1.g, wVertex, blending) - #else - TRI_BLEND(_5, alphaBlend1.g, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_6 - #ifdef NORMALMAP_6 - TRI_BLEND_NORMAL(_6, alphaBlend1.b, wVertex, blending) - #else - TRI_BLEND(_6, alphaBlend1.b, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_7 - #ifdef NORMALMAP_7 - TRI_BLEND_NORMAL(_7, alphaBlend1.a, wVertex, blending) - #else - TRI_BLEND(_7, alphaBlend1.a, wVertex, blending) - #endif - - #endif - #endif - - #ifdef ALPHAMAP_2 - #ifdef ALBEDOMAP_8 - #ifdef NORMALMAP_8 - TRI_BLEND_NORMAL(_8, alphaBlend2.r, wVertex, blending) - #else - TRI_BLEND(_8, alphaBlend2.r, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_9 - #ifdef NORMALMAP_9 - TRI_BLEND_NORMAL(_9, alphaBlend2.g, wVertex, blending) - #else - TRI_BLEND(_9, alphaBlend2.g, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_10 - #ifdef NORMALMAP_10 - TRI_BLEND_NORMAL(_10, alphaBlend2.b, wVertex, blending) - #else - TRI_BLEND(_10, alphaBlend2.b, wVertex, blending) - #endif - - #endif - #ifdef ALBEDOMAP_11 - #ifdef NORMALMAP_11 - TRI_BLEND_NORMAL(_11, alphaBlend2.a, wVertex, blending) - #else - TRI_BLEND(_11, alphaBlend2.a, wVertex, blending) - #endif - - #endif - #endif - - - - return albedo; - } - - - #endif - -#endif - - - -//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the -// vertex colors, or it can be set as a static value for an entire material with StaticSunIntensity float param -#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) - varying vec4 vertColors; -#endif - -#ifdef STATIC_SUN_INTENSITY - uniform float m_StaticSunIntensity; -#endif -//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the -//sun is more/less bright than the brightest point light to determine how the light probe's ambient light should be scaled later on.. -float brightestPointLight = 0.0; - - - void main(){ - #ifdef USE_FOG fogDistance = distance(g_CameraPosition, wPosition.xyz); #endif - float indoorSunLightExposure = 1.0; + indoorSunLightExposure = 1.0; viewDir = normalize(g_CameraPosition - wPosition); norm = normalize(wNormal); normal = norm; - - afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is saturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) + afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is sturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) #ifdef AFFLICTIONTEXTURE #ifdef TILELOCATION - //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z dimensions that the AfflictionAlphaMap represents).. + //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z dimesnions that the AfflictionAlphaMap represents) vec2 tileCoords; float xPos, zPos; vec3 locInTile = (wPosition - m_TileLocation); - locInTile += vec3(m_TileWidth/2, 0, m_TileWidth/2); + locInTile += vec3(m_TileWidth/2, 0, m_TileWidth/2); - xPos = (locInTile.x / m_TileWidth); - zPos = 1 - (locInTile.z / m_TileWidth); + xPos = (locInTile.x / m_TileWidth); + zPos = 1 - (locInTile.z / m_TileWidth); tileCoords = vec2(xPos, zPos); afflictionVector = texture2D(m_AfflictionAlphaMap, tileCoords).rgba; - - - #else - // .. otherwise when terrain size matches tileWidth, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap + // ..othrewise when terrain size matches tileWidth and location matches tileLocation, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap afflictionVector = texture2D(m_AfflictionAlphaMap, texCoord.xy).rgba; #endif #endif @@ -691,28 +201,96 @@ void main(){ livelinessValue = afflictionVector.r; afflictionValue = afflictionVector.g; + #ifdef ALBEDOMAP_0 + #ifdef ALPHAMAP + + vec4 alphaBlend; + vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; + int texChannelForAlphaBlending; + alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + vec2 texSlotCoords; - vec3 blending; - #ifdef ALBEDOMAP_0 - #ifdef ALPHAMAP - #ifdef TRI_PLANAR_MAPPING - blending = abs( norm ); - blending = (blending -0.2) * 0.7; - blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) - float b = (blending.x + blending.y + blending.z); - blending /= vec3(b, b, b); + float finalAlphaBlendForLayer = 1.0; + vec3 blending = abs( norm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); - albedo = calculateTriPlanarAlbedoBlend(norm, wVertex, texCoord, blending); - - #else - albedo = calculateAlbedoBlend(texCoord); + #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) + + //assign texture slot's blending from index's correct alpha map + if($i <= 3){ + alphaBlend = alphaBlend_0; + }else if($i <= 7){ + alphaBlend = alphaBlend_1; + }else if($i <= 11){ + alphaBlend = alphaBlend_2; + } + + texChannelForAlphaBlending = int(mod($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; + break; + case 1: + finalAlphaBlendForLayer = alphaBlend.g; + break; + case 2: + finalAlphaBlendForLayer = alphaBlend.b; + break; + case 3: + finalAlphaBlendForLayer = alphaBlend.a; + break; + } + + afflictionMode = m_AfflictionMode_$i; + + #ifdef TRI_PLANAR_MAPPING + //tri planar + tempAlbedo = getTriPlanarBlend(wVertex, blending, m_AlbedoMap_$i, m_AlbedoMap_$i_scale); + + #ifdef NORMALMAP_$i + tempNormal.rgb = getTriPlanarBlend(wVertex, blending, m_NormalMap_$i, m_AlbedoMap_$i_scale).rgb; + tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal);// this gets rid of the need for pre-generating tangents for TerrainPatches, since doing so doesn't seem to work (tbnMat is always blank for terrains even with tangents pre-generated, not sure why...) + #else + tempNormal.rgb = wNormal.rgb; + #endif + #else + + // non triplanar + texSlotCoords = texCoord * m_AlbedoMap_$i_scale; + + tempAlbedo.rgb = texture2D(m_AlbedoMap_$i, texSlotCoords).rgb; + #ifdef NORMALMAP_$i + tempNormal.xyz = texture2D(m_NormalMap_$i, texSlotCoords).xyz; + tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal); + #else + tempNormal.rgb = wNormal.rgb; + #endif + #endif + + //note: most of these functions can be found in AfflictionLib.glslib + tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting + + //mix values from this index layer to final output values based on finalAlphaBlendForLayer + albedo.rgb = mix(albedo.rgb, tempAlbedo.rgb , finalAlphaBlendForLayer); + normal.rgb = mix(normal.rgb, tempNormal.rgb, finalAlphaBlendForLayer); + Metallic = mix(Metallic, m_Metallic_$i, finalAlphaBlendForLayer); + Roughness = mix(Roughness, m_Roughness_$i, finalAlphaBlendForLayer); + + #endfor #endif - #else - albedo = texture2D(m_AlbedoMap_0, texCoord); - #endif #endif @@ -728,11 +306,9 @@ void main(){ #ifdef AFFLICTIONTEXTURE vec4 afflictionAlbedo; - float newAfflictionScale = m_AfflictionSplatScale; vec2 newScaledCoords; - #ifdef AFFLICTIONALBEDOMAP #ifdef TRI_PLANAR_MAPPING newAfflictionScale = newAfflictionScale / 256; @@ -784,25 +360,25 @@ void main(){ #endif float adjustedAfflictionValue = afflictionValue; - #ifdef USE_SPLAT_NOISE - noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); + #ifdef USE_SPLAT_NOISE + noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); - adjustedAfflictionValue = getAdjustedAfflictionVar(afflictionValue); - if(afflictionValue >= 0.99){ - adjustedAfflictionValue = afflictionValue; - } - #else - noiseHash = 1.0; - #endif - - Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); - Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); - albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); - normal = alterAfflictionNormalsForTerrain(adjustedAfflictionValue, normal, afflictionNormal, noiseHash , wNormal); - emissive = alterAfflictionGlow(adjustedAfflictionValue, emissive, afflictionEmissive, noiseHash); - emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); - emissiveIntensity *= afflictionEmissive.a; - //affliction ao value blended below after specular calculation + adjustedAfflictionValue = getAdjustedAfflictionVar(afflictionValue); + if(afflictionValue >= 0.99){ + adjustedAfflictionValue = afflictionValue; + } + #else + noiseHash = 1.0; + #endif + + Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); + Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); + albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); + normal = alterAfflictionNormalsForTerrain(adjustedAfflictionValue, normal, afflictionNormal, noiseHash , wNormal); + emissive = alterAfflictionGlow(adjustedAfflictionValue, emissive, afflictionEmissive, noiseHash); + emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); + emissiveIntensity *= afflictionEmissive.a; + //affliction ao value blended below after specular calculation #endif // spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used @@ -813,10 +389,7 @@ vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metalli vec4 diffuseColor = albedo - albedo * Metallic; vec3 fZero = vec3(specular); - gl_FragColor.rgb = vec3(0.0); - - //simple ao calculation, no support for lightmaps like stock pbr shader.. (probably could add lightmap support with another texture array, but // that would add another texture read per slot and require removing 12 other defines to make room...) @@ -827,20 +400,19 @@ gl_FragColor.rgb = vec3(0.0); #endif ao.rgb = ao.rrr; specularColor.rgb *= ao; - - #ifdef STATIC_SUN_INTENSITY indoorSunLightExposure = m_StaticSunIntensity; //single float value to indicate percentage of - //sunlight hitting the model (only works for small models or models with 100% consistent sunlight across every pixel) + //sunlight hitting the model (only works for small models or models with 100% consistent sunlighting accross every pixel) #endif #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY indoorSunLightExposure = vertColors.r * indoorSunLightExposure; //use R channel of vertexColors for.. #endif - // similar purpose as above... - //but uses r channel vert colors like an AO map specifically - //for sunlight (solution for scaling lighting for indoor - // and shady/dimly lit models, especially big ones with) + // similar purpose as above... + //but uses r channel vert colors like an AO map specifically + //for sunlight (solution for scaling lighting for indoor + // and shadey/dimly lit models, especially big ones that + // span accross varying directionalLight exposure) brightestPointLight = 0.0; @@ -884,18 +456,14 @@ gl_FragColor.rgb = vec3(0.0); } #endif - - - - gl_FragColor.rgb += directLighting * fallOff; - + + gl_FragColor.rgb += directLighting * fallOff; } - - + float minVertLighting; #ifdef BRIGHTEN_INDOOR_SHADOWS - minVertLighting = 0.0833; //brighten shadows so that caves which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) + minVertLighting = 0.0833; //brighten shadows so that caves/indoors which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) #else minVertLighting = 0.0533; @@ -904,8 +472,6 @@ gl_FragColor.rgb = vec3(0.0); indoorSunLightExposure = max(indoorSunLightExposure, brightestPointLight); indoorSunLightExposure = max(indoorSunLightExposure, minVertLighting); //scale the indoorSunLightExposure back up to account for the brightest point light nearby before scaling light probes by this value below - - #if NB_PROBES >= 1 vec3 color1 = vec3(0.0); vec3 color2 = vec3(0.0); @@ -955,26 +521,19 @@ gl_FragColor.rgb = vec3(0.0); // nearby point/spot lights ( will be multiplied by 1.0 and left unchanged if you are not defining any of the sunlight exposure variables for dimming indoors areas) color1.rgb *= indoorSunLightExposure; color2.rgb *= indoorSunLightExposure; - color3.rgb *= indoorSunLightExposure; - + color3.rgb *= indoorSunLightExposure; gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); #endif - - - if(emissive.a > 0){ - - emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; - + if(emissive.a > 0){ + emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; } // emissive = emissive * pow(emissiveIntensity * 2.3, emissive.a); gl_FragColor += emissive; - - // add fog after the lighting because shadows will cause the fog to darken // which just results in the geometry looking like it's changed color @@ -993,31 +552,24 @@ gl_FragColor.rgb = vec3(0.0); //outputs the final value of the selected layer as a color for debug purposes. #ifdef DEBUG_VALUES_MODE if(m_DebugValuesMode == 0){ - gl_FragColor.rgb = vec3(albedo); - + gl_FragColor.rgb = vec3(albedo); } else if(m_DebugValuesMode == 1){ - gl_FragColor.rgb = vec3(normal); - + gl_FragColor.rgb = vec3(normal); } else if(m_DebugValuesMode == 2){ - gl_FragColor.rgb = vec3(Roughness); - + gl_FragColor.rgb = vec3(Roughness); } else if(m_DebugValuesMode == 3){ - gl_FragColor.rgb = vec3(Metallic); - + gl_FragColor.rgb = vec3(Metallic); } else if(m_DebugValuesMode == 4){ - gl_FragColor.rgb = ao.rgb; - + gl_FragColor.rgb = ao.rgb; } else if(m_DebugValuesMode == 5){ - gl_FragColor.rgb = vec3(emissive.rgb); - } - + gl_FragColor.rgb = vec3(emissive.rgb); + } #endif - gl_FragColor.a = albedo.a; } From ebf8a8124dc4c2bf5b702b946679bd95e0f6ce60 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 10 Jan 2023 22:20:03 -0800 Subject: [PATCH 071/140] add the Spatial.addControlAt() method (#1899) --- .../src/main/java/com/jme3/scene/Spatial.java | 34 ++++- .../test/java/com/jme3/scene/SpatialTest.java | 122 ++++++++++++++++++ 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 jme3-core/src/test/java/com/jme3/scene/SpatialTest.java diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index eacaadb7aff..069da795fad 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -784,6 +784,38 @@ public void addControl(Control control) { } } + /** + * Adds the specified control to the list, at the specified index. Any + * controls with indices greater than or equal to the specified index will + * have their indices increased by one. + * + * @param index the index at which to add the control (0→first, ≥0) + * @param control the control to add (not null) + * @throws IllegalStateException if the control is already added here + */ + @SuppressWarnings("unchecked") + public void addControlAt(int index, Control control) { + if (control == null) { + throw new IllegalArgumentException("null control"); + } + int numControls = getNumControls(); + if (index < 0 || index > numControls) { + throw new IndexOutOfBoundsException( + "index=" + index + " for numControls=" + numControls); + } + if (controls.contains(control)) { + throw new IllegalStateException("Control is already added here."); + } + + addControl(control); // takes care of the bookkeeping + + if (index < numControls) { // re-arrange the list directly + boolean success = controls.remove(control); + assert success; + controls.add(index, control); + } + } + /** * Removes the first control that is an instance of the given class. * diff --git a/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java new file mode 100644 index 00000000000..038bdc3fc89 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java @@ -0,0 +1,122 @@ +/* + * 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.scene; + +import com.jme3.scene.control.UpdateControl; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests selected methods of the Spatial class. + * + * @author Stephen Gold + */ +public class SpatialTest { + + /** + * Tests addControlAt() with a duplicate Control. + */ + @Test(expected = IllegalStateException.class) + public void addControlAtDuplicate() { + Spatial testSpatial = new Node("testSpatial"); + UpdateControl control1 = new UpdateControl(); + testSpatial.addControlAt(0, control1); + testSpatial.addControlAt(1, control1); + } + + /** + * Tests addControlAt() with a negative index. + */ + @Test(expected = IndexOutOfBoundsException.class) + public void addControlAtNegativeIndex() { + Spatial testSpatial = new Node("testSpatial"); + UpdateControl control1 = new UpdateControl(); + testSpatial.addControlAt(-1, control1); + } + + /** + * Tests addControlAt() with a null argument. + */ + @Test(expected = IllegalArgumentException.class) + public void addControlAtNullControl() { + Spatial testSpatial = new Node("testSpatial"); + testSpatial.addControlAt(0, null); + } + + /** + * Tests addControlAt() with an out-of-range positive index. + */ + @Test(expected = IndexOutOfBoundsException.class) + public void addControlAtOutOfRange() { + Spatial testSpatial = new Node("testSpatial"); + UpdateControl control1 = new UpdateControl(); + testSpatial.addControlAt(1, control1); + } + + /** + * Tests typical uses of addControlAt(). + */ + @Test + public void testAddControlAt() { + Spatial testSpatial = new Node("testSpatial"); + + // Add to an empty list. + UpdateControl control1 = new UpdateControl(); + testSpatial.addControlAt(0, control1); + + Assert.assertEquals(1, testSpatial.getNumControls()); + Assert.assertEquals(control1, testSpatial.getControl(0)); + Assert.assertEquals(testSpatial, control1.getSpatial()); + + // Add at the end of a non-empty list. + UpdateControl control2 = new UpdateControl(); + testSpatial.addControlAt(1, control2); + + Assert.assertEquals(2, testSpatial.getNumControls()); + Assert.assertEquals(control1, testSpatial.getControl(0)); + Assert.assertEquals(control2, testSpatial.getControl(1)); + Assert.assertEquals(testSpatial, control1.getSpatial()); + Assert.assertEquals(testSpatial, control2.getSpatial()); + + // Add at the beginning of a non-empty list. + UpdateControl control0 = new UpdateControl(); + testSpatial.addControlAt(0, control0); + + Assert.assertEquals(3, testSpatial.getNumControls()); + Assert.assertEquals(control0, testSpatial.getControl(0)); + Assert.assertEquals(control1, testSpatial.getControl(1)); + Assert.assertEquals(control2, testSpatial.getControl(2)); + Assert.assertEquals(testSpatial, control0.getSpatial()); + Assert.assertEquals(testSpatial, control1.getSpatial()); + Assert.assertEquals(testSpatial, control2.getSpatial()); + } +} From 29e07614ec8d10ebc90f1a997382a267b6667e04 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 10 Jan 2023 22:24:37 -0800 Subject: [PATCH 072/140] JmeContext: add a getSystemListener() method (#1894) --- .../java/com/jme3/system/android/OGLESContext.java | 12 +++++++++++- .../src/main/java/com/jme3/system/JmeContext.java | 9 ++++++++- .../src/main/java/com/jme3/system/NullContext.java | 12 +++++++++++- .../src/main/java/com/jme3/system/AWTContext.java | 12 +++++++++++- .../java/com/jme3/system/awt/AwtPanelsContext.java | 12 +++++++++++- .../main/java/com/jme3/system/ios/IGLESContext.java | 13 ++++++++++++- .../java/com/jme3/system/lwjgl/LwjglContext.java | 12 +++++++++++- .../java/com/jme3/system/lwjgl/LwjglContext.java | 12 +++++++++++- .../java/com/jme3/system/lwjgl/LwjglContextVR.java | 12 +++++++++++- 9 files changed, 97 insertions(+), 9 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index a80eb7cccd6..cad19f71876 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -257,6 +257,16 @@ public void setSettings(AppSettings settings) { } } + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return listener; + } + @Override public void setSystemListener(SystemListener listener) { this.listener = listener; diff --git a/jme3-core/src/main/java/com/jme3/system/JmeContext.java b/jme3-core/src/main/java/com/jme3/system/JmeContext.java index f54831919af..6f1544c3e43 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeContext.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -92,6 +92,13 @@ public enum Type { */ public void setSettings(AppSettings settings); + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + public SystemListener getSystemListener(); + /** * Sets the listener that will receive events relating to context * creation, update, and destroy. diff --git a/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index c18de491292..9b7c0ed7b92 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,6 +64,16 @@ public Type getType() { return Type.Headless; } + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return listener; + } + @Override public void setSystemListener(SystemListener listener){ this.listener = listener; diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java index d72ee71c82e..7b6634d0324 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -149,6 +149,16 @@ public void setSettings(AppSettings settings) { this.backgroundContext.setSettings(settings); } + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return backgroundContext.getSystemListener(); + } + @Override public void setSystemListener(final SystemListener listener) { backgroundContext.setSystemListener(listener); diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java index 111c664eda2..18c6c5aac9b 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -120,6 +120,16 @@ public Type getType() { return Type.OffscreenSurface; } + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return listener; + } + @Override public void setSystemListener(SystemListener listener) { this.listener = listener; diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java index dfbcc9c4a6e..ba13a4a0afe 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -83,6 +83,17 @@ public void setSettings(AppSettings settings) { } } + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + logger.log(Level.FINE, "IGLESContext getSystemListener"); + return listener; + } + @Override public void setSystemListener(SystemListener listener) { logger.log(Level.FINE, "IGLESContext setSystemListener"); diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 60986d04324..d441254b393 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,6 +91,16 @@ public abstract class LwjglContext implements JmeContext { protected LwjglPlatform clPlatform; protected com.jme3.opencl.lwjgl.LwjglContext clContext; + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return listener; + } + @Override public void setSystemListener(SystemListener listener) { this.listener = listener; diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 84aa6e387d6..0a0a6bcd54f 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -133,6 +133,16 @@ public abstract class LwjglContext implements JmeContext { protected com.jme3.opencl.lwjgl.LwjglContext clContext; + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return listener; + } + @Override public void setSystemListener(final SystemListener listener) { this.listener = listener; diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java index c32f5607922..aa02e3ab19c 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java +++ b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java @@ -1,7 +1,7 @@ package com.jme3.system.lwjgl; /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,6 +79,16 @@ public abstract class LwjglContextVR implements JmeContext { protected Timer timer; protected SystemListener listener; + /** + * Accesses the listener that receives events related to this context. + * + * @return the pre-existing instance + */ + @Override + public SystemListener getSystemListener() { + return listener; + } + @Override public void setSystemListener(SystemListener listener) { this.listener = listener; From ed475e761fbece58682ad6657a45c371643ea082 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Wed, 11 Jan 2023 13:49:49 -0800 Subject: [PATCH 073/140] jme3-examples: add tests for issue #1903 --- .../light/pbr/TestIssue1903Compat.java | 91 +++++++++++++++++++ .../jme3test/light/pbr/TestIssue1903Core.java | 71 +++++++++++++++ .../src/main/resources/TestIssue1903.j3m | 8 ++ 3 files changed, 170 insertions(+) create mode 100644 jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Compat.java create mode 100644 jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Core.java create mode 100644 jme3-examples/src/main/resources/TestIssue1903.j3m diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Compat.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Compat.java new file mode 100644 index 00000000000..e169d27850c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Compat.java @@ -0,0 +1,91 @@ +/* + * 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 jme3test.light.pbr; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.CenterQuad; +import com.jme3.system.AppSettings; + +/** + * Reproduces an issue where PBR materials render much darker with the Core 3.2 + * profile than with the Compatibility profile. + * + *

This test relies on AppSettings set in main(), so it shouldn't be run + * from the jme3-examples TestChooser! + * + *

Compare the window rendered by this test with that rendered by + * TestIssue1903Core. If they differ, you have reproduced the issue. + * If they are identical, then you haven't reproduced it. + */ +public class TestIssue1903Compat extends SimpleApplication { + /** + * Main entry point for the TestIssue1903Compat application. + * + * @param unused array of command-line arguments + */ + public static void main(String[] unused) { + boolean loadDefaults = true; + AppSettings appSettings = new AppSettings(loadDefaults); + appSettings.setGammaCorrection(true); + appSettings.setRenderer(AppSettings.LWJGL_OPENGL2); // Compatibility profile + appSettings.setTitle("Compatibility"); + + TestIssue1903Compat application = new TestIssue1903Compat(); + application.setSettings(appSettings); + application.setShowSettings(false); // to speed up testing + application.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + + // Attach a 9x9 quad at the origin. + Mesh mesh = new CenterQuad(9f, 9f); + Geometry quad = new Geometry("quad", mesh); + rootNode.attachChild(quad); + + // Apply a PBR material to the quad. + String materialAssetPath = "TestIssue1903.j3m"; + Material material = assetManager.loadMaterial(materialAssetPath); + quad.setMaterial(material); + + // Add a LightProbe. + String lightProbePath = "Scenes/LightProbes/quarry_Probe.j3o"; + LightProbe probe = (LightProbe) assetManager.loadAsset(lightProbePath); + rootNode.addLight(probe); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Core.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Core.java new file mode 100644 index 00000000000..22bcc18e26b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1903Core.java @@ -0,0 +1,71 @@ +/* + * 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 jme3test.light.pbr; + +import com.jme3.app.SimpleApplication; +import com.jme3.system.AppSettings; + +/** + * Reproduces an issue where PBR materials render much darker with the Core 3.2 + * profile than with the Compatibility profile. + * + *

This test relies on AppSettings set in main(), so it shouldn't be run + * from the jme3-examples TestChooser! + * + *

Compare the window rendered by this test with that rendered by + * TestIssue1903Compat. If they differ, you have reproduced the issue. + * If they are identical, then you haven't reproduced it. + */ +public class TestIssue1903Core extends SimpleApplication { + /** + * Main entry point for the TestIssue1903Core application. + * + * @param unused array of command-line arguments + */ + public static void main(String[] unused) { + boolean loadDefaults = true; + AppSettings appSettings = new AppSettings(loadDefaults); + appSettings.setGammaCorrection(true); + appSettings.setRenderer(AppSettings.LWJGL_OPENGL32); // Core 3.2 profile + appSettings.setTitle("Core 3.2"); + + TestIssue1903Compat application = new TestIssue1903Compat(); + application.setSettings(appSettings); + application.setShowSettings(false); // to speed up testing + application.start(); + } + + @Override + public void simpleInitApp() { + throw new AssertionError(); // never reached + } +} diff --git a/jme3-examples/src/main/resources/TestIssue1903.j3m b/jme3-examples/src/main/resources/TestIssue1903.j3m new file mode 100644 index 00000000000..bf9386fd54e --- /dev/null +++ b/jme3-examples/src/main/resources/TestIssue1903.j3m @@ -0,0 +1,8 @@ +// PBR material used in TestIssue1903Compat and TestIssue1903Core + +Material TestIssue1903: Common/MatDefs/Light/PBRLighting.j3md { + MaterialParameters { + Metallic: 0.01 + NormalMap: Repeat Textures/Terrain/BrickWall/BrickWall_normal.jpg + } +} \ No newline at end of file From 58c137f59736751aab053496e89b13de5878f686 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Thu, 12 Jan 2023 00:44:26 -0500 Subject: [PATCH 074/140] Refactored Advanced PBR Terrain to use new for-loops (#1904) * Refactored Advanced PBR Terrain to use new for-loops Similar to my recent pull request doing the same for the base PbrTerrain shader (https://github.com/jMonkeyEngine/jmonkeyengine/pull/1901) this PR also adds for-loop support to the advanced version of the pbr shader that uses texture arrays * Update PBRTerrain.frag * Update AdvancedPBRTerrain.frag --- .../MatDefs/Terrain/AdvancedPBRTerrain.frag | 1291 ++++------------- .../Common/MatDefs/Terrain/PBRTerrain.frag | 2 +- 2 files changed, 289 insertions(+), 1004 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 664d86212f2..14899a0eca0 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag @@ -4,81 +4,71 @@ #import "Common/ShaderLib/Lighting.glsllib" #import "Common/MatDefs/Terrain/AfflictionLib.glsllib" - +varying vec3 wPosition; +varying vec3 vNormal; +varying vec2 texCoord; +uniform vec3 g_CameraPosition; +varying vec3 vPosition; +varying vec3 vnPosition; +varying vec3 vViewDir; +varying vec4 vLightDir; +varying vec4 vnLightDir; +varying vec3 lightVec; +varying vec3 inNormal; +varying vec3 wNormal; #ifdef DEBUG_VALUES_MODE - uniform int m_DebugValuesMode; + uniform int m_DebugValuesMode; #endif uniform vec4 g_LightData[NB_LIGHTS]; - uniform vec4 g_AmbientLightColor; -varying vec3 wPosition; -varying vec3 vNormal; -varying vec2 texCoord; - #if NB_PROBES >= 1 - uniform samplerCube g_PrefEnvMap; - uniform vec3 g_ShCoeffs[9]; - uniform mat4 g_LightProbeData; + uniform samplerCube g_PrefEnvMap; + uniform vec3 g_ShCoeffs[9]; + uniform mat4 g_LightProbeData; #endif #if NB_PROBES >= 2 - uniform samplerCube g_PrefEnvMap2; - uniform vec3 g_ShCoeffs2[9]; - uniform mat4 g_LightProbeData2; + uniform samplerCube g_PrefEnvMap2; + uniform vec3 g_ShCoeffs2[9]; + uniform mat4 g_LightProbeData2; #endif #if NB_PROBES == 3 - uniform samplerCube g_PrefEnvMap3; - uniform vec3 g_ShCoeffs3[9]; - uniform mat4 g_LightProbeData3; + uniform samplerCube g_PrefEnvMap3; + uniform vec3 g_ShCoeffs3[9]; + uniform mat4 g_LightProbeData3; #endif -vec2 newTexCoord; - - -uniform vec3 g_CameraPosition; - - -#ifdef USE_FOG - #import "Common/ShaderLib/MaterialFog.glsllib" - uniform vec4 m_FogColor; - float fogDistance; - - uniform vec2 m_LinearFog; -#endif - -#ifdef FOG_EXP - uniform float m_ExpFog; -#endif - -#ifdef FOG_EXPSQ - uniform float m_ExpSqFog; +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; #endif - - - -varying vec3 vPosition; -varying vec3 vnPosition; -varying vec3 vViewDir; -varying vec4 vLightDir; -varying vec4 vnLightDir; -varying vec3 lightVec; -varying vec3 inNormal; - -vec3 norm; - +//texture arrays: uniform sampler2DArray m_AlbedoTextureArray; uniform sampler2DArray m_NormalParallaxTextureArray; uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray; +//texture-slot params for 12 unique texture slots (0-11) where the integer value points to the desired texture's index in the corresponding texture array: +#for i=0..12 ( $0 ) + uniform int m_AfflictionMode_$i; + uniform float m_Roughness_$i; + uniform float m_Metallic_$i; + uniform float m_AlbedoMap_$i_scale; + uniform vec4 m_EmissiveColor_$i; + + #ifdef ALBEDOMAP_$i + uniform int m_AlbedoMap_$i; + #endif + #ifdef NORMALMAP_$i + uniform int m_NormalMap_$i; + #endif + #ifdef METALLICROUGHNESSMAP_$i + uniform int m_MetallicRoughnessMap_$i; + #endif +#endfor -#ifdef DISCARD_ALPHA - uniform float m_AlphaDiscardThreshold; -#endif - - +//3 alpha maps : #ifdef ALPHAMAP uniform sampler2D m_AlphaMap; #endif @@ -89,898 +79,93 @@ uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray; uniform sampler2D m_AlphaMap_2; #endif - - -uniform float m_Roughness_0; -uniform float m_Roughness_1; -uniform float m_Roughness_2; -uniform float m_Roughness_3; -uniform float m_Roughness_4; -uniform float m_Roughness_5; -uniform float m_Roughness_6; -uniform float m_Roughness_7; -uniform float m_Roughness_8; -uniform float m_Roughness_9; -uniform float m_Roughness_10; -uniform float m_Roughness_11; - -uniform float m_Metallic_0; -uniform float m_Metallic_1; -uniform float m_Metallic_2; -uniform float m_Metallic_3; -uniform float m_Metallic_4; -uniform float m_Metallic_5; -uniform float m_Metallic_6; -uniform float m_Metallic_7; -uniform float m_Metallic_8; -uniform float m_Metallic_9; -uniform float m_Metallic_10; -uniform float m_Metallic_11; - - -uniform vec4 m_EmissiveColor_0; -uniform vec4 m_EmissiveColor_1; -uniform vec4 m_EmissiveColor_2; -uniform vec4 m_EmissiveColor_3; -uniform vec4 m_EmissiveColor_4; -uniform vec4 m_EmissiveColor_5; -uniform vec4 m_EmissiveColor_6; -uniform vec4 m_EmissiveColor_7; -uniform vec4 m_EmissiveColor_8; -uniform vec4 m_EmissiveColor_9; -uniform vec4 m_EmissiveColor_10; -uniform vec4 m_EmissiveColor_11; - - - -#ifdef ALBEDOMAP_0 - uniform int m_AlbedoMap_0; -#endif -#ifdef ALBEDOMAP_1 - uniform int m_AlbedoMap_1; -#endif -#ifdef ALBEDOMAP_2 - uniform int m_AlbedoMap_2; -#endif -#ifdef ALBEDOMAP_3 - uniform int m_AlbedoMap_3; -#endif -#ifdef ALBEDOMAP_4 - uniform int m_AlbedoMap_4; -#endif -#ifdef ALBEDOMAP_5 - uniform int m_AlbedoMap_5; -#endif -#ifdef ALBEDOMAP_6 - uniform int m_AlbedoMap_6; -#endif -#ifdef ALBEDOMAP_7 - uniform int m_AlbedoMap_7; -#endif -#ifdef ALBEDOMAP_8 - uniform int m_AlbedoMap_8; -#endif -#ifdef ALBEDOMAP_9 - uniform int m_AlbedoMap_9; -#endif -#ifdef ALBEDOMAP_10 - uniform int m_AlbedoMap_10; -#endif -#ifdef ALBEDOMAP_11 - uniform int m_AlbedoMap_11; -#endif - - - - -uniform float m_AlbedoMap_0_scale; -uniform float m_AlbedoMap_1_scale; -uniform float m_AlbedoMap_2_scale; -uniform float m_AlbedoMap_3_scale; -uniform float m_AlbedoMap_4_scale; -uniform float m_AlbedoMap_5_scale; -uniform float m_AlbedoMap_6_scale; -uniform float m_AlbedoMap_7_scale; -uniform float m_AlbedoMap_8_scale; -uniform float m_AlbedoMap_9_scale; -uniform float m_AlbedoMap_10_scale; -uniform float m_AlbedoMap_11_scale; - - -#ifdef NORMALMAP_0 - uniform int m_NormalMap_0; -#endif -#ifdef NORMALMAP_1 - uniform int m_NormalMap_1; -#endif -#ifdef NORMALMAP_2 - uniform int m_NormalMap_2; -#endif -#ifdef NORMALMAP_3 - uniform int m_NormalMap_3; -#endif -#ifdef NORMALMAP_4 - uniform int m_NormalMap_4; -#endif -#ifdef NORMALMAP_5 - uniform int m_NormalMap_5; -#endif -#ifdef NORMALMAP_6 - uniform int m_NormalMap_6; -#endif -#ifdef NORMALMAP_7 - uniform int m_NormalMap_7; -#endif -#ifdef NORMALMAP_8 - uniform int m_NormalMap_8; -#endif -#ifdef NORMALMAP_9 - uniform int m_NormalMap_9; -#endif -#ifdef NORMALMAP_10 - uniform int m_NormalMap_10; -#endif -#ifdef NORMALMAP_11 - uniform int m_NormalMap_11; +#ifdef DISCARD_ALPHA + uniform float m_AlphaDiscardThreshold; #endif +//fog vars for basic fog : +#ifdef USE_FOG +#import "Common/ShaderLib/MaterialFog.glsllib" + uniform vec4 m_FogColor; + float fogDistance; -#ifdef METALLICROUGHNESSMAP_0 - uniform int m_MetallicRoughnessMap_0; -#endif -#ifdef METALLICROUGHNESSMAP_1 - uniform int m_MetallicRoughnessMap_1; -#endif -#ifdef METALLICROUGHNESSMAP_2 - uniform int m_MetallicRoughnessMap_2; -#endif -#ifdef METALLICROUGHNESSMAP_3 - uniform int m_MetallicRoughnessMap_3; -#endif -#ifdef METALLICROUGHNESSMAP_4 - uniform int m_MetallicRoughnessMap_4; -#endif -#ifdef METALLICROUGHNESSMAP_5 - uniform int m_MetallicRoughnessMap_5; -#endif -#ifdef METALLICROUGHNESSMAP_6 - uniform int m_MetallicRoughnessMap_6; -#endif -#ifdef METALLICROUGHNESSMAP_7 - uniform int m_MetallicRoughnessMap_7; -#endif -#ifdef METALLICROUGHNESSMAP_8 - uniform int m_MetallicRoughnessMap_8; + uniform vec2 m_LinearFog; #endif -#ifdef METALLICROUGHNESSMAP_9 - uniform int m_MetallicRoughnessMap_9; -#endif -#ifdef METALLICROUGHNESSMAP_10 - uniform int m_MetallicRoughnessMap_10; +#ifdef FOG_EXP + uniform float m_ExpFog; #endif -#ifdef METALLICROUGHNESSMAP_11 - uniform int m_MetallicRoughnessMap_11; +#ifdef FOG_EXPSQ + uniform float m_ExpSqFog; #endif - +//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the +// vertex colors, or it can be set as a static value for an entire material with the StaticSunIntensity float param #if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) - varying vec4 vertColors; + varying vec4 vertColors; #endif #ifdef STATIC_SUN_INTENSITY uniform float m_StaticSunIntensity; #endif - +//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the +//sun is more/less bright than the brightest point light for each fragment to determine how the light probe's ambient light should be scaled later on in light calculation code float brightestPointLight = 0.0; - - -vec4 afflictionVector; -//Optional 'Affliction' variables (used for dynamic desaturation and texture splatting) -#ifdef AFFLICTIONTEXTURE +//optional affliction paramaters that use the AfflictionAlphaMap's green channel for splatting m_SplatAlbedoMap and the red channel for splatting desaturation : +#ifdef AFFLICTIONTEXTURE uniform sampler2D m_AfflictionAlphaMap; #endif - #ifdef USE_SPLAT_NOISE uniform float m_SplatNoiseVar; #endif - -//defined for sub terrains that arent equal to each map tile size +//only defined for non-terrain geoemtries and terrains that are not positioned nor sized in correlation to the 2d array of AfflictionAlphaMaps used for splatting accross large tile based scenes in a grid #ifdef TILELOCATION uniform float m_TileWidth; uniform vec3 m_TileLocation; #endif - -uniform int m_AfflictionSplatScale; #ifdef AFFLICTIONALBEDOMAP - uniform sampler2D m_SplatAlbedoMap ; + uniform sampler2D m_SplatAlbedoMap; #endif - #ifdef AFFLICTIONNORMALMAP uniform sampler2D m_SplatNormalMap; #endif - #ifdef AFFLICTIONROUGHNESSMETALLICMAP uniform sampler2D m_SplatRoughnessMetallicMap; #endif - #ifdef AFFLICTIONEMISSIVEMAP uniform sampler2D m_SplatEmissiveMap; #endif +uniform int m_AfflictionSplatScale; uniform float m_AfflictionRoughnessValue; uniform float m_AfflictionMetallicValue; uniform float m_AfflictionEmissiveValue; uniform vec4 m_AfflictionEmissiveColor; +vec4 afflictionVector; +float noiseHash; +float livelinessValue; +float afflictionValue; +int afflictionMode = 1; - -uniform int m_AfflictionMode_0; -uniform int m_AfflictionMode_1; -uniform int m_AfflictionMode_2; -uniform int m_AfflictionMode_3; -uniform int m_AfflictionMode_4; -uniform int m_AfflictionMode_5; -uniform int m_AfflictionMode_6; -uniform int m_AfflictionMode_7; -uniform int m_AfflictionMode_8; -uniform int m_AfflictionMode_9; -uniform int m_AfflictionMode_10; -uniform int m_AfflictionMode_11; - - - - - - - varying vec3 wNormal; - -#ifdef TRI_PLANAR_MAPPING - varying vec4 wVertex; - -#endif +//general temp vars : +vec4 tempAlbedo, tempNormal, tempEmissiveColor; +float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; vec3 viewDir; - - - - vec2 coord; -vec4 albedo; +vec4 albedo = vec4(1.0); vec3 normal = vec3(0.5,0.5,1); -vec3 newNormal; +vec3 norm; float Metallic; float Roughness; float packedAoValue = 1.0; vec4 emissive; float emissiveIntensity = 1.0; - -vec4 packedMetallicRoughnessAoEiVec; -vec4 packedNormalParallaxVec; - -vec4 tempAlbedo, tempNormal, tempEmissiveColor; -float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; - -float noiseHash; -float livelinessValue; -float afflictionValue; -int afflictionMode = 1; - - -#define DEFINE_COORD(index) vec2 coord##index = texCoord * m_AlbedoMap##index##_scale; - - - - -#define BLEND_MR_VALUES(index, ab)\ - packedAoValue = mix(packedAoValue , 1, ab);\ - Metallic = mix(Metallic, m_Metallic##index, ab);\ - Roughness = mix(Roughness, m_Roughness##index, ab);\ - emissive = mix(emissive, m_EmissiveColor##index, ab); - -#define BLEND_MRAOEI_MAP(index, ab)\ - packedMetallicRoughnessAoEiVec.rgba = texture2DArray(m_MetallicRoughnessAoEiTextureArray, vec3(coord##index, m_MetallicRoughnessMap##index)).rgba;\ - tempRoughness = packedMetallicRoughnessAoEiVec.g* m_Roughness##index;\ - tempMetallic = m_Metallic##index;\ - tempMetallic = tempMetallic * packedMetallicRoughnessAoEiVec.b;\ - tempAo = packedMetallicRoughnessAoEiVec.r;\ - tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a;\ - emissiveIntensity = mix(emissiveIntensity, tempEmissiveIntensity, ab);\ - tempEmissiveColor = m_EmissiveColor##index;\ - tempEmissiveColor *= tempEmissiveIntensity;\ - packedAoValue = mix(packedAoValue, tempAo, ab);\ - Metallic = mix(Metallic, tempMetallic, ab);\ - Roughness = mix(Roughness, tempRoughness, ab);\ - emissive = mix(emissive, tempEmissiveColor, ab); - - - - -#define BLEND(index, ab)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo.rgb = texture2DArray(m_AlbedoTextureArray, vec3(coord##index, m_AlbedoMap##index)).rgb;\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo.rgb = mix( albedo.rgb, tempAlbedo.rgb ,ab );\ - normal.rgb = mix(normal.xyz, wNormal.rgb, ab); +float indoorSunLightExposure = 1.0; - - -#define BLEND_NORMAL(index, ab)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo.rgb = texture2DArray(m_AlbedoTextureArray, vec3(coord##index, m_AlbedoMap##index)).rgb;\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo.rgb = mix( albedo.rgb, tempAlbedo.rgb ,ab );\ - packedNormalParallaxVec.rgba = texture2DArray(m_NormalParallaxTextureArray, vec3(coord##index, m_NormalMap##index)).rgba;\ - tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal);\ - normal.xyz = mix(normal.xyz, tempNormal.xyz, ab); - - -#define TRI_BLEND_MR_VALUES(index, ab)\ - packedAoValue = mix(packedAoValue , 1, ab);\ - Metallic = mix(Metallic, m_Metallic##index, ab);\ - Roughness = mix(Roughness, m_Roughness##index, ab);\ - emissive = mix(emissive, m_EmissiveColor##index, ab); - -#define TRI_BLEND_MRAOEI_MAP(worldCoords, index, ab, blending)\ - packedMetallicRoughnessAoEiVec.rgba = getTriPlanarBlendFromTexArray(worldCoords, blending, m_MetallicRoughnessMap##index, m_AlbedoMap##index##_scale, m_MetallicRoughnessAoEiTextureArray).rgba;\ - tempRoughness = packedMetallicRoughnessAoEiVec.g* m_Roughness##index;\ - tempMetallic = m_Metallic##index;\ - tempMetallic = tempMetallic * packedMetallicRoughnessAoEiVec.b;\ - tempAo = packedMetallicRoughnessAoEiVec.r;\ - tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a;\ - emissiveIntensity = mix(emissiveIntensity, tempEmissiveIntensity, ab);\ - tempEmissiveColor = m_EmissiveColor##index;\ - tempEmissiveColor *= tempEmissiveIntensity;\ - packedAoValue = mix(packedAoValue, tempAo, ab);\ - Metallic = mix(Metallic, tempMetallic, ab);\ - Roughness = mix(Roughness, tempRoughness, ab);\ - emissive = mix(emissive, tempEmissiveColor, ab); - -#define TRI_BLEND(index, ab, worldCoords, blending)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo = getTriPlanarBlendFromTexArray(worldCoords, blending, m_AlbedoMap##index, m_AlbedoMap##index##_scale, m_AlbedoTextureArray);\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo = mix( albedo, tempAlbedo ,ab );\ - normal.rgb = mix(normal.xyz, wNormal.rgb, ab); - - - -#define TRI_BLEND_NORMAL(index, ab, worldCoords, blending)\ - afflictionMode = m_AfflictionMode##index;\ - tempAlbedo.rgb = getTriPlanarBlendFromTexArray(worldCoords, blending, m_AlbedoMap##index, m_AlbedoMap##index##_scale, m_AlbedoTextureArray).rgb;\ - packedNormalParallaxVec.rgba = getTriPlanarBlendFromTexArray(worldCoords, blending, m_NormalMap##index, m_AlbedoMap##index##_scale, m_NormalParallaxTextureArray).rgba;\ - tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode);\ - albedo.rgb = mix( albedo.rgb, tempAlbedo.rgb ,ab );\ - tempNormal.xyz = packedNormalParallaxVec.xyz;\ - tempNormal.xyz = calculateTangentsAndApplyToNormals(tempNormal.xyz, wNormal);\ - normal.xyz = mix(normal.xyz, tempNormal.xyz, ab); - - - - - - - #define BLEND_PARALLAX(index, ab)\ - albedo.r = alvedo.r; - - - -#ifdef ALPHAMAP - -//parallax removed for now -// calculateParallax(coord##index, m_ParallaxHeight##index, ab, m_NormalMap##index); - -void calculateParallax(inout vec2 parallaxTexCoord, in float parallaxHeight, in float intensity, in int texIndex) { -// #ifdef PARALLAX_OCCLUSION -// #ifdef PARALLAX_LOD_DISTANCE -// if(camDist < m_ParallaxLODDistance && intensity > 0.2){ -// vec3 vViewDir = viewDir * tbnMat; -// Parallax_initFor(vViewDir,parallaxHeight); -// Parallax_TextureArray_displaceCoords(parallaxTexCoord, m_NormalParallaxTextureArray, texIndex); -// } -// #else -// if(intensity > 0.2){ -// vec3 vViewDir = viewDir * tbnMat; -// Parallax_initFor(vViewDir,parallaxHeight); -// Parallax_TextureArray_displaceCoords(parallaxTexCoord, m_NormalParallaxTextureArray, texIndex); -// } -// #endif - // #else -// #ifdef STEEP_PARALLAX - //parallax map is stored in the alpha channel of the normal map - // newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); - // #else - //parallax map is stored in the alpha channel of the normal map -// newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); -// #endif - - // #endif -} - -void calculateTriParallax(inout vec2 parallaxTexCoord, in float parallaxHeight, in float intensity, in int texIndex) { -// #ifdef PARALLAX_OCCLUSION -// #ifdef PARALLAX_LOD_DISTANCE -// if(camDist < m_ParallaxLODDistance && intensity > 0.2){ -// vec3 vViewDir = viewDir * tbnMat; -// Parallax_initFor(vViewDir,parallaxHeight); -// Tri_Parallax_TextureArray_displaceCoords(parallaxTexCoord, m_NormalParallaxTextureArray, texIndex); -// } -// #else -// if(intensity > 0.2){ -// vec3 vViewDir = viewDir * tbnMat; -// Parallax_initFor(vViewDir,parallaxHeight); -// Tri_Parallax_TextureArray_displaceCoords(parallaxTexCoord, m_NormalParallaxTextureArray, texIndex); -// } -// #endif - // #else -// #ifdef STEEP_PARALLAX - //parallax map is stored in the alpha channel of the normal map - // newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); - // #else - //parallax map is stored in the alpha channel of the normal map -// newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); -// #endif - - // #endif -} - - -vec4 getTriPlanarBlendFromTexArray(in vec4 coords, in vec3 blending, in int idInTexArray, in float scale, in sampler2DArray texArray) { - - - vec4 col1 = texture2DArray( texArray, vec3((coords.yz * scale), idInTexArray ) ); - vec4 col2 = texture2DArray( texArray, vec3((coords.xz * scale), idInTexArray ) ); - vec4 col3 = texture2DArray( texArray, vec3((coords.xy * scale), idInTexArray ) ); - // blend the results of the 3 planar projections. - vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; - - return tex; -} - -vec4 calculateAlbedoBlend(in vec2 texCoord) { - vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); - vec4 albedo = vec4(1.0); - - - - Roughness = m_Roughness_0; - Metallic = m_Metallic_0 ; - - vec3 blending = abs( wNormal ); - blending = (blending -0.2) * 0.7; - blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) - float b = (blending.x + blending.y + blending.z); - blending /= vec3(b, b, b); - - - #ifdef ALPHAMAP_1 - vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); - #endif - #ifdef ALPHAMAP_2 - vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); - #endif - #ifdef ALBEDOMAP_0 - //NOTE! the old (phong) TerrainLighting.j3md shaders do not have an "_0" for the first diffuse map, it is just "DiffuseMap" - DEFINE_COORD(_0) - #ifdef PARALLAXHEIGHT_0 - BLEND_PARALLAX(_0, alphaBlend.r) - #endif - - #ifdef NORMALMAP_0 - BLEND_NORMAL(_0, alphaBlend.r) - #else - BLEND(_0, alphaBlend.r) - #endif - #ifdef METALLICROUGHNESSMAP_0 - BLEND_MRAOEI_MAP(_0, alphaBlend.r) - #else - BLEND_MR_VALUES(_0, alphaBlend.r) - #endif - - #endif - #ifdef ALBEDOMAP_1 - DEFINE_COORD(_1) - #ifdef PARALLAXHEIGHT_1 - BLEND_PARALLAX(_1, alphaBlend.g) - #endif - - #ifdef NORMALMAP_1 - BLEND_NORMAL(_1, alphaBlend.g) - #else - BLEND(_1, alphaBlend.g) - #endif - #ifdef METALLICROUGHNESSMAP_1 - BLEND_MRAOEI_MAP(_1, alphaBlend.g) - #else - BLEND_MR_VALUES(_1, alphaBlend.g) - #endif - - #endif - #ifdef ALBEDOMAP_2 - DEFINE_COORD(_2) - - #ifdef PARALLAXHEIGHT_2 - BLEND_PARALLAX(_2, alphaBlend.b) - #endif - - #ifdef NORMALMAP_2 - BLEND_NORMAL(_2, alphaBlend.b) - #else - BLEND(_2, alphaBlend.b) - #endif - #ifdef METALLICROUGHNESSMAP_2 - BLEND_MRAOEI_MAP(_2, alphaBlend.b) - #else - BLEND_MR_VALUES(_2, alphaBlend.b) - #endif - - - #endif - #ifdef ALBEDOMAP_3 - DEFINE_COORD(_3) - #ifdef PARALLAXHEIGHT_3 - BLEND_PARALLAX(_3, alphaBlend.a) - #endif - - #ifdef NORMALMAP_3 - BLEND_NORMAL(_3, alphaBlend.a) - #else - BLEND(_3, alphaBlend.a) - #endif - #ifdef METALLICROUGHNESSMAP_3 - BLEND_MRAOEI_MAP(_3, alphaBlend.a) - #else - BLEND_MR_VALUES(_3, alphaBlend.a) - #endif - #endif - - #ifdef ALPHAMAP_1 - #ifdef ALBEDOMAP_4 - DEFINE_COORD(_4) - #ifdef PARALLAXHEIGHT_4 - BLEND_PARALLAX(_4, alphaBlend.r) - #endif - - #ifdef NORMALMAP_4 - BLEND_NORMAL(_4, alphaBlend1.r) - #else - BLEND(_4, alphaBlend1.r) - #endif - #ifdef METALLICROUGHNESSMAP_4 - BLEND_MRAOEI_MAP(_4, alphaBlend1.r) - #else - BLEND_MR_VALUES(_4, alphaBlend1.r) - #endif - - #endif - #ifdef ALBEDOMAP_5 - DEFINE_COORD(_5) - #ifdef PARALLAXHEIGHT_5 - BLEND_PARALLAX(_5, alphaBlend.g) - #endif - #ifdef NORMALMAP_5 - BLEND_NORMAL(_5, alphaBlend1.g) - #else - BLEND(_5, alphaBlend1.g) - #endif - #ifdef METALLICROUGHNESSMAP_5 - BLEND_MRAOEI_MAP(_5, alphaBlend1.g) - #else - BLEND_MR_VALUES(_5, alphaBlend1.g) - #endif - - #endif - #ifdef ALBEDOMAP_6 - DEFINE_COORD(_6) - #ifdef PARALLAXHEIGHT_6 - BLEND_PARALLAX(_6, alphaBlend.b) - #endif - #ifdef NORMALMAP_6 - BLEND_NORMAL(_6, alphaBlend1.b) - #else - BLEND(_6, alphaBlend1.b) - #endif - #ifdef METALLICROUGHNESSMAP_6 - BLEND_MRAOEI_MAP(_6, alphaBlend1.b) - #else - BLEND_MR_VALUES(_6, alphaBlend1.b) - #endif - - - #endif - #ifdef ALBEDOMAP_7 - DEFINE_COORD(_7) - #ifdef PARALLAXHEIGHT_7 - BLEND_PARALLAX(_7, alphaBlend.a) - #endif - #ifdef NORMALMAP_7 - BLEND_NORMAL(_7, alphaBlend1.a) - #else - BLEND(_7, alphaBlend1.a) - #endif - #ifdef METALLICROUGHNESSMAP_7 - BLEND_MRAOEI_MAP(_7, alphaBlend1.a) - #else - BLEND_MR_VALUES(_7, alphaBlend1.a) - #endif - - #endif - #endif - - #ifdef ALPHAMAP_2 - #ifdef ALBEDOMAP_8 - DEFINE_COORD(_8) - #ifdef PARALLAXHEIGHT_8 - BLEND_PARALLAX(_8, alphaBlend.r) - #endif - #ifdef NORMALMAP_8 - BLEND_NORMAL(_8, alphaBlend2.r) - #else - BLEND(_8, alphaBlend2.r) - #endif - #ifdef METALLICROUGHNESSMAP_8 - BLEND_MRAOEI_MAP(_8, alphaBlend2.r) - #else - BLEND_MR_VALUES(_8, alphaBlend2.r) - #endif - - #endif - #ifdef ALBEDOMAP_9 - DEFINE_COORD(_9) - #ifdef PARALLAXHEIGHT_9 - BLEND_PARALLAX(_9, alphaBlend.g) - #endif - #ifdef NORMALMAP_9 - BLEND_NORMAL(_9, alphaBlend2.g) - #else - BLEND(_9, alphaBlend2.g) - #endif - #ifdef METALLICROUGHNESSMAP_8 - BLEND_MRAOEI_MAP(_9, alphaBlend2.g) - #else - BLEND_MR_VALUES(_9, ,alphaBlend2.g) - #endif - - #endif - #ifdef ALBEDOMAP_10 - DEFINE_COORD(_10) - #ifdef PARALLAXHEIGHT_10 - BLEND_PARALLAX(_10, alphaBlend.b) - #endif - #ifdef NORMALMAP_10 - BLEND_NORMAL(_10, alphaBlend2.b) - #else - BLEND(_10, alphaBlend2.b) - #endif - #ifdef METALLICROUGHNESSMAP_8 - BLEND_MRAOEI_MAP(_10, alphaBlend2.b) - #else - BLEND_MR_VALUES(_10, alphaBlend2.b) - #endif - - #endif - #ifdef ALBEDOMAP_11 - DEFINE_COORD(_11) - #ifdef PARALLAXHEIGHT_11 - BLEND_PARALLAX(_11, alphaBlend.a) - #endif - #ifdef NORMALMAP_11 - BLEND_NORMAL(_11, alphaBlend2.a) - #else - BLEND(_11, alphaBlend2.a) - #endif - #ifdef METALLICROUGHNESSMAP_8 - BLEND_MRAOEI_MAP(_11, alphaBlend2.a) - #else - BLEND_MR_VALUES(_11, alphaBlend2.a) - #endif - - #endif - #endif - - return albedo; - } - - -// TRI PLANAR ALPHA MAP TEXTURES_ _ _ _ \/ - - #ifdef TRI_PLANAR_MAPPING - - - vec4 calculateTriPlanarAlbedoBlend(in vec3 wNorm, in vec4 wVert, in vec2 texCoord, vec3 blending) { - // tri-planar texture bending factor for this fragment's normal - vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); - vec4 albedo = vec4(1.0); - - - - Roughness = m_Roughness_0; - Metallic = m_Metallic_0 ; - - - - - #ifdef ALPHAMAP_1 - vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); - #endif - #ifdef ALPHAMAP_2 - vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); - #endif - - #ifdef ALBEDOMAP_0 - //NOTE! the old (phong) terrain shaders do not have an "_0" for the first diffuse map, it is just "DiffuseMap" - #ifdef NORMALMAP_0 - TRI_BLEND_NORMAL(_0, alphaBlend.r, wVertex, blending) - #else - TRI_BLEND(_0, alphaBlend.r, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_0 - TRI_BLEND_MRAOEI_MAP(wVertex,_0, alphaBlend.r, blending) - #else - TRI_BLEND_MR_VALUES(_0, alphaBlend.r) - #endif - - #endif - #ifdef ALBEDOMAP_1 - #ifdef NORMALMAP_1 - TRI_BLEND_NORMAL(_1, alphaBlend.g, wVertex, blending) - #else - TRI_BLEND(_1, alphaBlend.g, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_1 - TRI_BLEND_MRAOEI_MAP(wVertex,_1, alphaBlend.g, blending) - #else - TRI_BLEND_MR_VALUES(_1, alphaBlend.g) - #endif - - #endif - #ifdef ALBEDOMAP_2 - #ifdef NORMALMAP_2 - TRI_BLEND_NORMAL(_2, alphaBlend.b, wVertex, blending) - #else - TRI_BLEND(_2, alphaBlend.b, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_2 - TRI_BLEND_MRAOEI_MAP(wVertex,_2, alphaBlend.b, blending) - #else - TRI_BLEND_MR_VALUES(_2, alphaBlend.b) - #endif - - #endif - #ifdef ALBEDOMAP_3 - #ifdef NORMALMAP_3 - TRI_BLEND_NORMAL(_3, alphaBlend.a, wVertex, blending) - #else - TRI_BLEND(_3, alphaBlend.a, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_3 - TRI_BLEND_MRAOEI_MAP(wVertex,_3, alphaBlend.a, blending) - #else - TRI_BLEND_MR_VALUES(_3, alphaBlend.a) - #endif - - #endif - - #ifdef ALPHAMAP_1 - #ifdef ALBEDOMAP_4 - #ifdef NORMALMAP_4 - TRI_BLEND_NORMAL(_4, alphaBlend1.r, wVertex, blending) - #else - TRI_BLEND(_4, alphaBlend1.r, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_4 - TRI_BLEND_MRAOEI_MAP(wVertex,_4, alphaBlend1.r, blending) - #else - TRI_BLEND_MR_VALUES(_4, alphaBlend1.r) - #endif - - #endif - #ifdef ALBEDOMAP_5 - #ifdef NORMALMAP_5 - TRI_BLEND_NORMAL(_5, alphaBlend1.g, wVertex, blending) - #else - TRI_BLEND(_5, alphaBlend1.g, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_5 - TRI_BLEND_MRAOEI_MAP(wVertex,_5, alphaBlend1.g, blending) - #else - TRI_BLEND_MR_VALUES(_5, alphaBlend1.g) - #endif - - #endif - #ifdef ALBEDOMAP_6 - #ifdef NORMALMAP_6 - TRI_BLEND_NORMAL(_6, alphaBlend1.b, wVertex, blending) - #else - TRI_BLEND(_6, alphaBlend1.b, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_6 - TRI_BLEND_MRAOEI_MAP(wVertex,_6, alphaBlend1.b, blending) - #else - TRI_BLEND_MR_VALUES(_6, alphaBlend1.b) - #endif - - #endif - #ifdef ALBEDOMAP_7 - #ifdef NORMALMAP_7 - TRI_BLEND_NORMAL(_7, alphaBlend1.a, wVertex, blending) - #else - TRI_BLEND(_7, alphaBlend1.a, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_7 - TRI_BLEND_MRAOEI_MAP(wVertex,_7, alphaBlend1.a, blending) - #else - TRI_BLEND_MR_VALUES(_7, alphaBlend1.a) - #endif - - #endif - #endif - - #ifdef ALPHAMAP_2 - #ifdef ALBEDOMAP_8 - #ifdef NORMALMAP_8 - TRI_BLEND_NORMAL(_8, alphaBlend2.r, wVertex, blending) - #else - TRI_BLEND(_8, alphaBlend2.r, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_8 - TRI_BLEND_MRAOEI_MAP(wVertex,_8, alphaBlend2.r, blending) - #else - TRI_BLEND_MR_VALUES(_8, alphaBlend2.r) - #endif - - #endif - #ifdef ALBEDOMAP_9 - #ifdef NORMALMAP_9 - TRI_BLEND_NORMAL(_9, alphaBlend2.g, wVertex, blending) - #else - TRI_BLEND(_9, alphaBlend2.g, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_9 - TRI_BLEND_MRAOEI_MAP(wVertex,_9, alphaBlend2.g, blending) - #else - TRI_BLEND_MR_VALUES(_9, alphaBlend2.g) - #endif - - #endif - #ifdef ALBEDOMAP_10 - #ifdef NORMALMAP_10 - TRI_BLEND_NORMAL(_10, alphaBlend2.b, wVertex, blending) - #else - TRI_BLEND(_10, alphaBlend2.b, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_10 - TRI_BLEND_MRAOEI_MAP(wVertex,_10, alphaBlend2.b, blending) - #else - TRI_BLEND_MR_VALUES(_10, alphaBlend2.b) - #endif - - #endif - #ifdef ALBEDOMAP_11 - #ifdef NORMALMAP_11 - TRI_BLEND_NORMAL(_11, alphaBlend2.a, wVertex, blending) - #else - TRI_BLEND(_11, alphaBlend2.a, wVertex, blending) - #endif - #ifdef METALLICROUGHNESSMAP_11 - TRI_BLEND_MRAOEI_MAP(wVertex,_11, alphaBlend2.a, blending) - #else - TRI_BLEND_MR_VALUES(_11, alphaBlend2.a) - #endif - - #endif - #endif - - - - return albedo; - } - - - #endif - -#endif - +vec4 packedMetallicRoughnessAoEiVec; +vec4 packedNormalParallaxVec; void main(){ @@ -997,12 +182,12 @@ void main(){ normal = norm; - afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is saturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) + afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is sturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) #ifdef AFFLICTIONTEXTURE #ifdef TILELOCATION - //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z dimensions that the AfflictionAlphaMap represents).. + //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z dimesnions that the AfflictionAlphaMap represents).. vec2 tileCoords; float xPos, zPos; @@ -1020,7 +205,7 @@ void main(){ #else - // ..otherwise when terrain size matches tileWidth, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap + // ..othrewise when terrain size matches tileWidth, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap afflictionVector = texture2D(m_AfflictionAlphaMap, texCoord.xy).rgba; #endif #endif @@ -1029,27 +214,139 @@ void main(){ afflictionValue = afflictionVector.g; + #ifdef ALBEDOMAP_0 + #ifdef ALPHAMAP + + vec4 alphaBlend; + vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; + int texChannelForAlphaBlending; + + alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec2 texSlotCoords; + + float finalAlphaBlendForLayer = 1.0; + + vec3 blending = abs( norm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) + + //assign texture slot's blending from index's correct alpha map + if($i <= 3){ + alphaBlend = alphaBlend_0; + }else if($i <= 7){ + alphaBlend = alphaBlend_1; + }else if($i <= 11){ + alphaBlend = alphaBlend_2; + } + + texChannelForAlphaBlending = int(mod($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; + break; + case 1: + finalAlphaBlendForLayer = alphaBlend.g; + break; + case 2: + finalAlphaBlendForLayer = alphaBlend.b; + break; + case 3: + finalAlphaBlendForLayer = alphaBlend.a; + break; + } + + afflictionMode = m_AfflictionMode_$i; + tempEmissiveColor = m_EmissiveColor_$i; + + #ifdef TRI_PLANAR_MAPPING + //tri planar + tempAlbedo = getTriPlanarBlendFromTexArray(wVertex, blending, m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray); + + #ifdef NORMALMAP_$i + packedNormalParallaxVec.rgba = getTriPlanarBlendFromTexArray(wVertex, blending, m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray).rgba; + tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal);// this gets rid of the need for pre-generating tangents for TerrainPatches, since doing so doesn't seem to work (tbnMat is always blank for terrains even with tangents pre-generated, not sure why...) + tempParallax = packedNormalParallaxVec.w; + + #ifdef PARALLAXHEIGHT_0 + //wip + #endif + #else + tempNormal.rgb = wNormal.rgb; + #endif + #ifdef METALLICROUGHNESSMAP_$i + packedMetallicRoughnessAoEiVec = getTriPlanarBlendFromTexArray(wVertex, blending, m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray).rgba; + tempRoughness = packedMetallicRoughnessAoEiVec.g * m_Roughness_$i; + tempMetallic = packedMetallicRoughnessAoEiVec.b * m_Metallic_$i; + tempAo = packedMetallicRoughnessAoEiVec.r; + tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a; + #endif + #else + + // non triplanar + texSlotCoords = texCoord * m_AlbedoMap_$i_scale; + + tempAlbedo = texture2DArray(m_AlbedoTextureArray, vec3(texSlotCoords, m_AlbedoMap_$i)); + + #ifdef NORMALMAP_$i + packedNormalParallaxVec = texture2DArray(m_NormalParallaxTextureArray, vec3(texSlotCoords, m_NormalMap_$i)); + tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal); + tempParallax = packedNormalParallaxVec.w; + + #ifdef PARALLAXHEIGHT_0 + //eventually add parallax code here if a PARALLAXHEIGHT_$i float is defined. but this shader is at the define limit currently, + // so to do that will require removing defines around scale to use that for enabling parallax per layer instead, since there's no reason for define around basic float scale anyways + #endif + #else + tempNormal.rgb = wNormal.rgb; + #endif + + #ifdef METALLICROUGHNESSMAP_$i + packedMetallicRoughnessAoEiVec = texture2DArray(m_MetallicRoughnessAoEiTextureArray, vec3(texSlotCoords, m_MetallicRoughnessMap_$i)); + tempRoughness = packedMetallicRoughnessAoEiVec.g * m_Roughness_$i; + tempMetallic = packedMetallicRoughnessAoEiVec.b * m_Metallic_$i; + tempAo = packedMetallicRoughnessAoEiVec.r; + tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a; + #endif + #endif + + + //blend to float values if no texture value for mrao map exists + #if !defined(METALLICROUGHNESSMAP_$i) + tempRoughness = m_Roughness_$i; + tempMetallic = m_Metallic_$i; + tempAo = 1.0; + #endif + //note: most of these functions can be found in AfflictionLib.glslib + tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting - vec3 blending; - #ifdef ALBEDOMAP_0 - #ifdef ALPHAMAP - #ifdef TRI_PLANAR_MAPPING - blending = abs( wNormal ); - blending = (blending -0.2) * 0.7; - blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) - float b = (blending.x + blending.y + blending.z); - blending /= vec3(b, b, b); + tempEmissiveColor *= tempEmissiveIntensity; + //mix values from this index layer to final output values based on finalAlphaBlendForLayer + albedo.rgb = mix(albedo.rgb, tempAlbedo.rgb , finalAlphaBlendForLayer); + normal.rgb = mix(normal.rgb, tempNormal.rgb, finalAlphaBlendForLayer); + Metallic = mix(Metallic, tempMetallic, finalAlphaBlendForLayer); + Roughness = mix(Roughness, tempRoughness, finalAlphaBlendForLayer); + packedAoValue = mix(packedAoValue, tempAo, finalAlphaBlendForLayer); + emissiveIntensity = mix(emissiveIntensity, tempEmissiveIntensity, finalAlphaBlendForLayer); + emissive = mix(emissive, tempEmissiveColor, finalAlphaBlendForLayer); - albedo = calculateTriPlanarAlbedoBlend(wNormal, wVertex, texCoord, blending); - + #endfor #else - albedo = calculateAlbedoBlend(texCoord); + albedo = texture2D(m_AlbedoMap_0, texCoord); #endif - #else - albedo = texture2D(m_AlbedoMap_0, texCoord); - #endif #endif float alpha = albedo.a; @@ -1057,82 +354,89 @@ void main(){ if(alpha < m_AlphaDiscardThreshold){ discard; } - #endif - + #endif - + //APPLY AFFLICTIONN TO THE PIXEL + #ifdef AFFLICTIONTEXTURE + vec4 afflictionAlbedo; -//APPLY AFFLICTION TO THE PIXEL -#ifdef AFFLICTIONTEXTURE -vec4 afflictionAlbedo; + float newAfflictionScale = m_AfflictionSplatScale; + vec2 newScaledCoords; -float newAfflictionScale = m_AfflictionSplatScale; -vec2 newScaledCoords; + #ifdef AFFLICTIONALBEDOMAP + #ifdef TRI_PLANAR_MAPPING + newAfflictionScale = newAfflictionScale / 256; + afflictionAlbedo = getTriPlanarBlend(wVertex, blending, m_SplatAlbedoMap , newAfflictionScale); + #else + newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); + afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); + #endif - #ifdef AFFLICTIONALBEDOMAP - #ifdef TRI_PLANAR_MAPPING - newAfflictionScale = newAfflictionScale / 256; - afflictionAlbedo = getTriPlanarBlend(wVertex, blending, m_SplatAlbedoMap , newAfflictionScale); #else - newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); - afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); + afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); #endif - #else - afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); - #endif + vec3 afflictionNormal; + #ifdef AFFLICTIONNORMALMAP + #ifdef TRI_PLANAR_MAPPING - vec3 afflictionNormal; - #ifdef AFFLICTIONNORMALMAP - #ifdef TRI_PLANAR_MAPPING + afflictionNormal = getTriPlanarBlend(wVertex, blending, m_SplatNormalMap , newAfflictionScale).rgb; - afflictionNormal = getTriPlanarBlend(wVertex, blending, m_SplatNormalMap , newAfflictionScale).rgb; + #else + afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; + #endif #else - afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; + afflictionNormal = norm; + #endif + float afflictionMetallic = m_AfflictionMetallicValue; + float afflictionRoughness = m_AfflictionRoughnessValue; + float afflictionAo = 1.0; - #else - afflictionNormal = norm; - #endif - float afflictionMetallic = m_AfflictionMetallicValue; - float afflictionRoughness = m_AfflictionRoughnessValue; - float afflictionAo = 1.0; + vec4 afflictionEmissive = m_AfflictionEmissiveColor; + float afflictionEmissiveIntensity = m_AfflictionEmissiveValue; + #ifdef AFFLICTIONROUGHNESSMETALLICMAP + vec4 metallicRoughnessAoEiVec; + #ifdef TRI_PLANAR_MAPPING + metallicRoughnessAoEiVec = texture2D(m_SplatRoughnessMetallicMap, newScaledCoords); + #else + metallicRoughnessAoEiVec = getTriPlanarBlend(wVertex, blending, m_SplatRoughnessMetallicMap, newAfflictionScale); + #endif - vec4 afflictionEmissive = m_AfflictionEmissiveColor; - float afflictionEmissiveIntensity = m_AfflictionEmissiveValue; - + afflictionRoughness *= metallicRoughnessAoEiVec.g; + afflictionMetallic *= metallicRoughnessAoEiVec.b; + afflictionAo = metallicRoughnessAoEiVec.r; + afflictionEmissiveIntensity *= metallicRoughnessAoEiVec.a; //important not to leave this channel all black by accident when creating the mraoei map if using affliction emissiveness - #ifdef AFFLICTIONROUGHNESSMETALLICMAP - vec4 metallicRoughnessAoEiVec = texture2D(m_SplatRoughnessMetallicMap, newScaledCoords); - afflictionRoughness *= metallicRoughnessAoEiVec.g; - afflictionMetallic *= metallicRoughnessAoEiVec.b; - afflictionAo = metallicRoughnessAoEiVec.r; - afflictionEmissiveIntensity *= metallicRoughnessAoEiVec.a; //important not to leave this channel all black by accident when creating the mraoei map if using affliction emissiveness + #endif - #endif + #ifdef AFFLICTIONEMISSIVEMAP + vec4 emissiveMapColor; + #ifdef TRI_PLANAR_MAPPING + emissiveMapColor = texture2D(m_SplatEmissiveMap, newScaledCoords); + #else + emissiveMapColor = getTriPlanarBlend(wVertex, blending, m_SplatEmissiveMap, newAfflictionScale); + #endif + afflictionEmissive *= emissiveMapColor; + #endif - #ifdef AFFLICTIONEMISSIVEMAP - vec4 emissiveMapColor = texture2D(m_SplatEmissiveMap, newScaledCoords); - afflictionEmissive *= emissiveMapColor; - #endif - - float adjustedAfflictionValue = afflictionValue; + float adjustedAfflictionValue = afflictionValue; #ifdef USE_SPLAT_NOISE - noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); - + noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); //VERY IMPORTANT to replace this with a noiseMap texture, as calculating noise per pixel in-shader like this does lower framerate a lot + adjustedAfflictionValue = getAdjustedAfflictionVar(afflictionValue); if(afflictionValue >= 0.99){ adjustedAfflictionValue = afflictionValue; } #else noiseHash = 1.0; - #endif - + #endif + Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); @@ -1141,20 +445,18 @@ vec2 newScaledCoords; emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); emissiveIntensity *= afflictionEmissive.a; //affliction ao value blended below after specular calculation -#endif - -// spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used - -float specular = 0.5; -float nonMetalSpec = 0.08 * specular; -vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; -vec4 diffuseColor = albedo - albedo * Metallic; -vec3 fZero = vec3(specular); - + + #endif -gl_FragColor.rgb = vec3(0.0); + // spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used + float specular = 0.5; + float nonMetalSpec = 0.08 * specular; + vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; + vec4 diffuseColor = albedo - albedo * Metallic; + vec3 fZero = vec3(specular); + gl_FragColor.rgb = vec3(0.0); //simple ao calculation, no support for lightmaps like stock pbr shader.. (probably could add lightmap support with another texture array, but // that would add another texture read per slot and require removing 12 other defines to make room...) @@ -1170,7 +472,7 @@ gl_FragColor.rgb = vec3(0.0); #ifdef STATIC_SUN_INTENSITY indoorSunLightExposure = m_StaticSunIntensity; //single float value to indicate percentage of - //sunlight hitting the model (only works for small models or models with 100% consistent sunlight across every pixel) + //sunlight hitting the model (only works for small models or models with 100% consistent sunlighting accross every pixel) #endif #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY indoorSunLightExposure = vertColors.r * indoorSunLightExposure; //use R channel of vertexColors for.. @@ -1178,7 +480,7 @@ gl_FragColor.rgb = vec3(0.0); // similar purpose as above... //but uses r channel vert colors like an AO map specifically //for sunlight (solution for scaling lighting for indoor - // and shady/dimly lit models, especially big ones with) + // and shadey/dimly lit models, especially big ones with) brightestPointLight = 0.0; @@ -1222,16 +524,11 @@ gl_FragColor.rgb = vec3(0.0); } - #endif - - - - gl_FragColor.rgb += directLighting * fallOff; + #endif - + gl_FragColor.rgb += directLighting * fallOff; } - - + float minVertLighting; #ifdef BRIGHTEN_INDOOR_SHADOWS minVertLighting = 0.0833; //brighten shadows so that caves which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) @@ -1243,8 +540,6 @@ gl_FragColor.rgb = vec3(0.0); indoorSunLightExposure = max(indoorSunLightExposure, brightestPointLight); indoorSunLightExposure = max(indoorSunLightExposure, minVertLighting); //scale the indoorSunLightExposure back up to account for the brightest point light nearby before scaling light probes by this value below - - #if NB_PROBES >= 1 vec3 color1 = vec3(0.0); vec3 color2 = vec3(0.0); @@ -1288,8 +583,7 @@ gl_FragColor.rgb = vec3(0.0); color2.rgb *= g_AmbientLightColor.rgb; color3.rgb *= g_AmbientLightColor.rgb; #endif - - + // multiply probes by the indoorSunLightExposure, as determined by pixel's sunlightExposure and adjusted for // nearby point/spot lights ( will be multiplied by 1.0 and left unchanged if you are not defining any of the sunlight exposure variables for dimming indoors areas) color1.rgb *= indoorSunLightExposure; @@ -1300,15 +594,13 @@ gl_FragColor.rgb = vec3(0.0); gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); #endif - - - + if(emissive.a > 0){ emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; } + // emissive = emissive * pow(emissiveIntensity * 2.3, emissive.a); gl_FragColor += emissive; - // add fog after the lighting because shadows will cause the fog to darken // which just results in the geometry looking like it's changed color @@ -1327,30 +619,23 @@ gl_FragColor.rgb = vec3(0.0); //outputs the final value of the selected layer as a color for debug purposes. #ifdef DEBUG_VALUES_MODE if(m_DebugValuesMode == 0){ - gl_FragColor.rgb = vec3(albedo); - + gl_FragColor.rgb = vec3(albedo); } else if(m_DebugValuesMode == 1){ - gl_FragColor.rgb = vec3(normal); - + gl_FragColor.rgb = vec3(normal); } else if(m_DebugValuesMode == 2){ - gl_FragColor.rgb = vec3(Roughness); - + gl_FragColor.rgb = vec3(Roughness); } else if(m_DebugValuesMode == 3){ - gl_FragColor.rgb = vec3(Metallic); - + gl_FragColor.rgb = vec3(Metallic); } else if(m_DebugValuesMode == 4){ - gl_FragColor.rgb = ao.rgb; - + gl_FragColor.rgb = ao.rgb; } else if(m_DebugValuesMode == 5){ - gl_FragColor.rgb = vec3(emissive.rgb); - - } - + gl_FragColor.rgb = vec3(emissive.rgb); + } #endif gl_FragColor.a = albedo.a; 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 4832bef04c7..f4dfa78cd17 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag @@ -2,7 +2,7 @@ #import "Common/ShaderLib/PBR.glsllib" #import "Common/ShaderLib/Parallax.glsllib" #import "Common/ShaderLib/Lighting.glsllib" -#import "MatDefs/ShaderLib/AfflictionLib.glsllib" +#import "Common/MatDefs/Terrain/AfflictionLib.glsllib" varying vec3 wPosition; varying vec3 vNormal; From 6ad716a57537104779b26b0bed89826a0a0f0888 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Thu, 12 Jan 2023 00:45:30 -0500 Subject: [PATCH 075/140] Update AfflictionLib.glsllib (#1905) This PR goes along with my last PR cleaning up the AdvancedPBRTerrain.j3md shader. The method getTriPlanarBlendFromTexArray() was put into this glsllib that contains all of the other commonly used functions for the pbr terrain shaders. --- .../Common/MatDefs/Terrain/AfflictionLib.glsllib | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib index 8718cd3842b..2c96614adbf 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib @@ -11,6 +11,16 @@ vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, in sampler2D map, in fl return tex; } +vec4 getTriPlanarBlendFromTexArray(in vec4 coords, in vec3 blending, in int idInTexArray, in float scale, in sampler2DArray texArray) { + vec4 col1 = texture2DArray( texArray, vec3((coords.yz * scale), idInTexArray ) ); + vec4 col2 = texture2DArray( texArray, vec3((coords.xz * scale), idInTexArray ) ); + vec4 col3 = texture2DArray( texArray, vec3((coords.xy * scale), idInTexArray ) ); + // blend the results of the 3 planar projections. + vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; + + return tex; +} + //used for mixing normal map normals with the world normals. texture slots without a normal map use wNormal as their blending value instead vec3 calculateTangentsAndApplyToNormals(in vec3 normalIn, in vec3 worldNorm){ From b0e6ce7ae871336fa2627c882ba67a603289b2dc Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Thu, 12 Jan 2023 23:12:38 +0330 Subject: [PATCH 076/140] common.gradle: set class files compatible with Java 8 using "release" option (#1907) * common.gradle: set class files compatible with Java 8. This will keep java 8 compatibility when it is compiled with newer java versions. * Merge with existing block. * Reformat code. --- common.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common.gradle b/common.gradle index 308b86f3c7d..b162de51b13 100644 --- a/common.gradle +++ b/common.gradle @@ -20,6 +20,9 @@ tasks.withType(JavaCompile) { // compile-time options: //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings options.compilerArgs << '-Xlint:unchecked' options.encoding = 'UTF-8' + if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_1_10)) { + options.release = 8 + } } ext { @@ -40,7 +43,6 @@ dependencies { testImplementation 'org.codehaus.groovy:groovy-test:3.0.14' } - // Uncomment if you want to see the status of every test that is run and // the test output. /* From 76a55d5e735ef35305caca478a4c3c527f00c858 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 15 Jan 2023 08:56:44 +0330 Subject: [PATCH 077/140] common.gradle: add "Created-By" jar manifest to show Java version and vendor name (#1913) * common.gradle: add Created-By in jar manifest to show java version used to create the jar. * Also add vendor name. --- common.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common.gradle b/common.gradle index b162de51b13..5961ec84b31 100644 --- a/common.gradle +++ b/common.gradle @@ -57,7 +57,8 @@ jar { manifest { attributes 'Implementation-Title': 'jMonkeyEngine', 'Implementation-Version': jmeFullVersion, - 'Automatic-Module-Name': "${project.name.replace("-", ".")}" + 'Automatic-Module-Name': "${project.name.replace("-", ".")}", + 'Created-By': "${JavaVersion.current()} (${System.getProperty("java.vendor")})" } } From 9a2d9505b17281f40570f4b2cfcf7621d0c65b65 Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Sun, 15 Jan 2023 06:28:18 +0100 Subject: [PATCH 078/140] ParticleEmitter: improve code readability. Apply the DRY principle (#1912) --- .../java/com/jme3/effect/ParticleEmitter.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 9dba41eb16f..a2d481f36bf 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -31,6 +31,8 @@ */ package com.jme3.effect; +import java.io.IOException; + import com.jme3.bounding.BoundingBox; import com.jme3.effect.ParticleMesh.Type; import com.jme3.effect.influencers.DefaultParticleInfluencer; @@ -56,7 +58,6 @@ import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; -import java.io.IOException; /** * ParticleEmitter is a special kind of geometry which simulates @@ -909,6 +910,7 @@ private Particle emitParticle(Vector3f min, Vector3f max) { p.size = startSize; //shape.getRandomPoint(p.position); particleInfluencer.influenceParticle(p, shape); + if (worldSpace) { worldTransform.transformVector(p.position, p.position); worldTransform.getRotation().mult(p.velocity, p.velocity); @@ -921,10 +923,8 @@ private Particle emitParticle(Vector3f min, Vector3f max) { p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f); } - temp.set(p.position).addLocal(p.size, p.size, p.size); - max.maxLocal(temp); - temp.set(p.position).subtractLocal(p.size, p.size, p.size); - min.minLocal(temp); + // Computing bounding volume + computeBoundingVolume(p, min, max); ++lastUsed; firstUnUsed = idx + 1; @@ -1038,15 +1038,19 @@ protected void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max) p.angle += p.rotateSpeed * tpf; // Computing bounding volume - temp.set(p.position).addLocal(p.size, p.size, p.size); - max.maxLocal(temp); - temp.set(p.position).subtractLocal(p.size, p.size, p.size); - min.minLocal(temp); + computeBoundingVolume(p, min, max); if (!selectRandomImage) { p.imageIndex = (int) (b * imagesX * imagesY); } } + + private void computeBoundingVolume(Particle p, Vector3f min, Vector3f max) { + temp.set(p.position).addLocal(p.size, p.size, p.size); + max.maxLocal(temp); + temp.set(p.position).subtractLocal(p.size, p.size, p.size); + min.minLocal(temp); + } private void updateParticleState(float tpf) { // Force world transform to update @@ -1180,16 +1184,14 @@ private void renderFromControl(RenderManager rm, ViewPort vp) { this.getMaterial().setFloat("Quadratic", C); } - Matrix3f inverseRotation = Matrix3f.IDENTITY; - TempVars vars = null; - if (!worldSpace) { - vars = TempVars.get(); - - inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal(); - } - particleMesh.updateParticleData(particles, cam, inverseRotation); if (!worldSpace) { + TempVars vars = TempVars.get(); + Matrix3f inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal(); + particleMesh.updateParticleData(particles, cam, inverseRotation); vars.release(); + + } else { + particleMesh.updateParticleData(particles, cam, Matrix3f.IDENTITY); } } From 925ff4561b29fff22fe2e47ecc63d129cb3a4000 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 14 Jan 2023 21:45:47 -0800 Subject: [PATCH 079/140] add 4 getters to JmeContext for screen position and frame-buffer size (#1911) --- .../com/jme3/system/android/OGLESContext.java | 60 +++++++++++++++++++ .../main/java/com/jme3/system/JmeContext.java | 31 ++++++++++ .../java/com/jme3/system/NullContext.java | 40 +++++++++++++ .../main/java/com/jme3/system/AWTContext.java | 44 ++++++++++++-- .../com/jme3/system/awt/AwtPanelsContext.java | 39 ++++++++++++ .../com/jme3/system/ios/IGLESContext.java | 40 +++++++++++++ .../com/jme3/system/lwjgl/LwjglContext.java | 44 ++++++++++++++ .../com/jme3/system/lwjgl/LwjglWindow.java | 50 +++++++++++++++- .../com/jme3/system/lwjgl/LwjglWindowVR.java | 53 +++++++++++++++- 9 files changed, 394 insertions(+), 7 deletions(-) diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index cad19f71876..5757368ef8b 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -37,10 +37,13 @@ import android.content.DialogInterface; import android.content.pm.ConfigurationInfo; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.opengl.GLSurfaceView; import android.os.Build; import android.text.InputType; import android.view.Gravity; +import android.view.SurfaceHolder; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.EditText; @@ -494,4 +497,61 @@ public com.jme3.opencl.Context getOpenCLContext() { logger.warning("OpenCL is not yet supported on android"); return null; } + + /** + * Returns the height of the input surface. + * + * @return the height (in pixels) + */ + @Override + public int getFramebufferHeight() { + Rect rect = getSurfaceFrame(); + int result = rect.height(); + return result; + } + + /** + * Returns the width of the input surface. + * + * @return the width (in pixels) + */ + @Override + public int getFramebufferWidth() { + Rect rect = getSurfaceFrame(); + int result = rect.width(); + return result; + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowXPosition() { + throw new UnsupportedOperationException("not implemented yet"); + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowYPosition() { + throw new UnsupportedOperationException("not implemented yet"); + } + + /** + * Retrieves the dimensions of the input surface. Note: do not modify the + * returned object. + * + * @return the dimensions (in pixels, left and top are 0) + */ + private Rect getSurfaceFrame() { + SurfaceView view = (SurfaceView) androidInput.getView(); + SurfaceHolder holder = view.getHolder(); + Rect result = holder.getSurfaceFrame(); + return result; + } } diff --git a/jme3-core/src/main/java/com/jme3/system/JmeContext.java b/jme3-core/src/main/java/com/jme3/system/JmeContext.java index 6f1544c3e43..8d83e0960e6 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeContext.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeContext.java @@ -194,4 +194,35 @@ public enum Type { */ public void destroy(boolean waitFor); + /** + * Returns the height of the framebuffer. + * + * @return the height (in pixels) + * @throws IllegalStateException for a headless or null context + */ + public int getFramebufferHeight(); + + /** + * Returns the width of the framebuffer. + * + * @return the width (in pixels) + * @throws IllegalStateException for a headless or null context + */ + public int getFramebufferWidth(); + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @return the screen X coordinate + * @throws IllegalStateException for a headless or null context + */ + public int getWindowXPosition(); + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @return the screen Y coordinate + * @throws IllegalStateException for a headless or null context + */ + public int getWindowYPosition(); } diff --git a/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index 9b7c0ed7b92..fd43d8d7615 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -266,4 +266,44 @@ public boolean isRenderable() { public Context getOpenCLContext() { return null; } + + /** + * Returns the height of the framebuffer. + * + * @throws UnsupportedOperationException + */ + @Override + public int getFramebufferHeight() { + throw new UnsupportedOperationException("null context"); + } + + /** + * Returns the width of the framebuffer. + * + * @throws UnsupportedOperationException + */ + @Override + public int getFramebufferWidth() { + throw new UnsupportedOperationException("null context"); + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowXPosition() { + throw new UnsupportedOperationException("null context"); + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowYPosition() { + throw new UnsupportedOperationException("null context"); + } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java index 7b6634d0324..1232f048094 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -38,11 +38,6 @@ import com.jme3.input.TouchInput; import com.jme3.opencl.Context; import com.jme3.renderer.Renderer; -import com.jme3.system.AppSettings; -import com.jme3.system.JmeContext; -import com.jme3.system.JmeSystem; -import com.jme3.system.SystemListener; -import com.jme3.system.Timer; /** * A JMonkey {@link JmeContext context} that is dedicated to AWT component rendering. @@ -241,4 +236,43 @@ public void destroy(final boolean waitFor) { backgroundContext.destroy(waitFor); } + /** + * Returns the height of the framebuffer. + * + * @return the height (in pixels) + */ + @Override + public int getFramebufferHeight() { + return height; + } + + /** + * Returns the width of the framebuffer. + * + * @return the width (in pixels) + */ + @Override + public int getFramebufferWidth() { + return width; + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowXPosition() { + throw new UnsupportedOperationException("not implemented yet"); + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowYPosition() { + throw new UnsupportedOperationException("not implemented yet"); + } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java index 18c6c5aac9b..a4132510995 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -286,4 +286,43 @@ public void restart() { // only relevant if changing pixel format. } + /** + * Returns the height of the input panel. + * + * @return the height (in pixels) + */ + @Override + public int getFramebufferHeight() { + return inputSource.getHeight(); + } + + /** + * Returns the width of the input panel. + * + * @return the width (in pixels) + */ + @Override + public int getFramebufferWidth() { + return inputSource.getWidth(); + } + + /** + * Returns the screen X coordinate of the left edge of the input panel. + * + * @return the screen X coordinate + */ + @Override + public int getWindowXPosition() { + return inputSource.getX(); + } + + /** + * Returns the screen Y coordinate of the top edge of the input panel. + * + * @return the screen Y coordinate + */ + @Override + public int getWindowYPosition() { + return inputSource.getY(); + } } diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java index ba13a4a0afe..009195c776e 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java @@ -227,4 +227,44 @@ public Context getOpenCLContext() { logger.warning("OpenCL not yet supported on this platform"); return null; } + + /** + * Returns the height of the framebuffer. + * + * @throws UnsupportedOperationException + */ + @Override + public int getFramebufferHeight() { + throw new UnsupportedOperationException("not implemented yet"); + } + + /** + * Returns the width of the framebuffer. + * + * @throws UnsupportedOperationException + */ + @Override + public int getFramebufferWidth() { + throw new UnsupportedOperationException("not implemented yet"); + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowXPosition() { + throw new UnsupportedOperationException("not implemented yet"); + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @throws UnsupportedOperationException + */ + @Override + public int getWindowYPosition() { + throw new UnsupportedOperationException("not implemented yet"); + } } \ No newline at end of file diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index d441254b393..8aed65f07c1 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -519,4 +519,48 @@ public Timer getTimer() { public com.jme3.opencl.Context getOpenCLContext() { return clContext; } + + /** + * Returns the height of the framebuffer. + * + * @return the height (in pixels) + */ + @Override + public int getFramebufferHeight() { + int result = Display.getHeight(); + return result; + } + + /** + * Returns the width of the framebuffer. + * + * @return the width (in pixels) + */ + @Override + public int getFramebufferWidth() { + int result = Display.getWidth(); + return result; + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @return the screen X coordinate + */ + @Override + public int getWindowXPosition() { + int result = Display.getX(); + return result; + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @return the screen Y coordinate + */ + @Override + public int getWindowYPosition() { + int result = Display.getY(); + return result; + } } 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 9962b7218dc..b0156d83323 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -799,4 +799,52 @@ public Vector2f getWindowContentScale(Vector2f store) { return store; } + + /** + * Returns the height of the framebuffer. + * + * @return the height (in pixels) + */ + @Override + public int getFramebufferHeight() { + glfwGetFramebufferSize(window, width, height); + int result = height[0]; + return result; + } + + /** + * Returns the width of the framebuffer. + * + * @return the width (in pixels) + */ + @Override + public int getFramebufferWidth() { + glfwGetFramebufferSize(window, width, height); + int result = width[0]; + return result; + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @return the screen X coordinate + */ + @Override + public int getWindowXPosition() { + glfwGetWindowPos(window, width, height); + int result = width[0]; + return result; + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @return the screen Y coordinate + */ + @Override + public int getWindowYPosition() { + glfwGetWindowPos(window, width, height); + int result = height[0]; + return result; + } } diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java index c43cc50c638..5c21cd6e92b 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java +++ b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java @@ -1,7 +1,7 @@ package com.jme3.system.lwjgl; /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,6 +81,10 @@ public abstract class LwjglWindowVR extends LwjglContextVR implements Runnable { private GLFWWindowFocusCallback windowFocusCallback; private Thread mainThread; + // reusable arrays for GLFW calls + final private int width[] = new int[1]; + final private int height[] = new int[1]; + /** * Create a new wrapper class over the GLFW framework in LWJGL 3. * @param type the {@link com.jme3.system.JmeContext.Type type} of the display. @@ -527,6 +531,53 @@ public long getWindowHandle() { return window; } + /** + * Returns the height of the framebuffer. + * + * @return the height (in pixels) + */ + @Override + public int getFramebufferHeight() { + glfwGetFramebufferSize(window, width, height); + int result = height[0]; + return result; + } + + /** + * Returns the width of the framebuffer. + * + * @return the width (in pixels) + */ + @Override + public int getFramebufferWidth() { + glfwGetFramebufferSize(window, width, height); + int result = width[0]; + return result; + } + + /** + * Returns the screen X coordinate of the left edge of the content area. + * + * @return the screen X coordinate + */ + @Override + public int getWindowXPosition() { + glfwGetWindowPos(window, width, height); + int result = width[0]; + return result; + } + + /** + * Returns the screen Y coordinate of the top edge of the content area. + * + * @return the screen Y coordinate + */ + @Override + public int getWindowYPosition() { + glfwGetWindowPos(window, width, height); + int result = height[0]; + return result; + } // TODO: Implement support for window icon when GLFW supports it. /* From d694f7a072578b05b49f092a6613ad01618ba4cd Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 14 Jan 2023 21:51:08 -0800 Subject: [PATCH 080/140] test and fix for #1909 (NPE while generating tangents) (#1910) * add a JUnit test for issue 1909 (NPE while generating tangents) * solve issue #1909 (NPE while generating tangents) --- .../jme3/util/mikktspace/MikkTSpaceImpl.java | 8 +- .../java/com/jme3/util/TestIssue1909.java | 97 +++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 jme3-core/src/test/java/com/jme3/util/TestIssue1909.java diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java index cb5912907c9..18417b6b4c9 100644 --- a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,9 +44,14 @@ public class MikkTSpaceImpl implements MikkTSpaceContext { Mesh mesh; + final private IndexBuffer index; public MikkTSpaceImpl(Mesh mesh) { this.mesh = mesh; + + // If the mesh lacks indices, generate a virtual index buffer. + this.index = mesh.getIndicesAsList(); + //replacing any existing tangent buffer, if you came here you want them new. mesh.clearBuffer(VertexBuffer.Type.Tangent); FloatBuffer fb = BufferUtils.createFloatBuffer(mesh.getVertexCount() * 4); @@ -115,7 +120,6 @@ public void setTSpace(float[] tangent, float[] biTangent, float magS, float magT } private int getIndex(int face, int vert) { - IndexBuffer index = mesh.getIndexBuffer(); int vertIndex = index.get(face * 3 + vert); return vertIndex; } diff --git a/jme3-core/src/test/java/com/jme3/util/TestIssue1909.java b/jme3-core/src/test/java/com/jme3/util/TestIssue1909.java new file mode 100644 index 00000000000..3cc91ef6f60 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/TestIssue1909.java @@ -0,0 +1,97 @@ +/* + * 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.util; + +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import java.nio.FloatBuffer; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that tangents can be generated without an index buffer. This was + * issue #1909 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue1909 { + /** + * Tests MikktspaceTangentGenerator.generate() without index buffers. + */ + @Test + public void testIssue1909() { + /* + * Generate normals, texture coordinates, and vertex positions + * for a large square in the X-Z plane. + */ + FloatBuffer normals = BufferUtils.createFloatBuffer( + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f + ); + float uvDiameter = 5f; + FloatBuffer uvs = BufferUtils.createFloatBuffer( + uvDiameter, uvDiameter, + 0f, 0f, + uvDiameter, 0f, + uvDiameter, uvDiameter, + 0f, uvDiameter, + 0f, 0f + ); + float posRadius = 500f; + FloatBuffer positions = BufferUtils.createFloatBuffer( + +posRadius, 0f, +posRadius, + -posRadius, 0f, -posRadius, + -posRadius, 0f, +posRadius, + +posRadius, 0f, +posRadius, + +posRadius, 0f, -posRadius, + -posRadius, 0f, -posRadius + ); + Mesh mesh = new Mesh(); + int numAxes = 3; + mesh.setBuffer(VertexBuffer.Type.Normal, numAxes, normals); + mesh.setBuffer(VertexBuffer.Type.Position, numAxes, positions); + mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); + mesh.updateBound(); + + Geometry testGeometry = new Geometry("testGeometry", mesh); + MikktspaceTangentGenerator.generate(testGeometry); + + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNotNull(tangents); + } +} From 6d83c63e5f864a61e3c10aeec449c41420765db9 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 15 Jan 2023 11:29:40 +0330 Subject: [PATCH 081/140] jme3-lwjgl: bump to lwjgl 2.9.5 (#1914) --- jme3-lwjgl/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-lwjgl/build.gradle b/jme3-lwjgl/build.gradle index f61ef7b06c5..846c2fd2544 100644 --- a/jme3-lwjgl/build.gradle +++ b/jme3-lwjgl/build.gradle @@ -2,8 +2,8 @@ dependencies { api project(':jme3-core') api project(':jme3-desktop') - api 'org.jmonkeyengine:lwjgl:2.9.4' - runtimeOnly 'org.jmonkeyengine:lwjgl-platform:2.9.4' + api 'org.jmonkeyengine:lwjgl:2.9.5' + runtimeOnly 'org.jmonkeyengine:lwjgl-platform:2.9.5' /* * Upgrades the default jinput-2.0.5 to jinput-2.0.9 to fix a bug with gamepads on Linux. From 5664d599ea0aa37bbcd08cf2869783f5a2a874b8 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Mon, 16 Jan 2023 08:34:44 +0330 Subject: [PATCH 082/140] Fix #1917 (RendererException in ScreenshotAppState: Attempting to upload empty buffer) (#1918) --- .../src/main/java/com/jme3/system/JmeDesktopSystem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 3a2882a241d..11c06c4622e 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -100,7 +100,7 @@ private static BufferedImage ensureOpaque(BufferedImage original) { @Override public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { - BufferedImage awtImage = ImageToAwt.convert(new Image(Image.Format.RGBA8, width, height, imageData, ColorSpace.Linear), false, true, 0); + BufferedImage awtImage = ImageToAwt.convert(new Image(Image.Format.RGBA8, width, height, imageData.duplicate(), ColorSpace.Linear), false, true, 0); awtImage = verticalFlip(awtImage); ImageWriter writer = ImageIO.getImageWritersByFormatName(format).next(); From c5405eaac2195bbd75fb9cc8286288c61a9b3ac9 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Wed, 18 Jan 2023 03:18:01 -0500 Subject: [PATCH 083/140] PBRLighting: fix comment describing packed MetallicRoughnessMap (#1921) Updated a comment about the MetallicRoughness map that previously said the Red channel is unused - changed to instead say that the red channel of MR map stores the AO value if AoPackedInMRMap is true. --- .../src/main/resources/Common/MatDefs/Light/PBRLighting.j3md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md index 56b12f166f6..633e57c5084 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -28,7 +28,7 @@ MaterialDef PBR Lighting { Texture2D RoughnessMap -LINEAR //Metallic and Roughness are packed respectively in the b and g channel of a single map - // r: unspecified + // r: AO (if AoPackedInMRMap is true) // g: Roughness // b: Metallic Texture2D MetallicRoughnessMap -LINEAR From 67d3edfa35d96118dd8b1d8b54a5193d5df9538c Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Wed, 18 Jan 2023 00:22:29 -0800 Subject: [PATCH 084/140] solve issue #1919 (underflow while generating tangents) (#1920) * jme-core: add a test for issue #1919 (underflow generating tangents) * MikktspaceTangentGenerator: solve #1919 (underflow generating tangents) --- .../MikktspaceTangentGenerator.java | 40 +++- .../java/com/jme3/util/TestIssue1919.java | 180 ++++++++++++++++++ 2 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 jme3-core/src/test/java/com/jme3/util/TestIssue1919.java 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 4f2dcfc87b6..27375469e4b 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -112,13 +112,39 @@ public static void generate(Spatial s){ for (Spatial child : n.getChildren()) { generate(child); } - } else if (s instanceof Geometry){ - Geometry g = (Geometry)s; - MikkTSpaceImpl context = new MikkTSpaceImpl(g.getMesh()); - if(!genTangSpaceDefault(context)){ - logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); + + } else if (s instanceof Geometry) { + Geometry g = (Geometry) s; + Mesh mesh = g.getMesh(); + + Mesh.Mode mode = mesh.getMode(); + boolean hasTriangles; + switch (mode) { + case Points: + case Lines: + case LineStrip: + case LineLoop: + hasTriangles = false; // skip this mesh + break; + + case Triangles: + case TriangleFan: + case TriangleStrip: + hasTriangles = true; + break; + + default: + String message = "Tangent generation isn't implemented for mode=" + mode; + throw new UnsupportedOperationException(message); + } + + if (hasTriangles) { + MikkTSpaceImpl context = new MikkTSpaceImpl(mesh); + if (!genTangSpaceDefault(context)) { + logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); + } + TangentUtils.generateBindPoseTangentsIfNecessary(mesh); } - TangentUtils.generateBindPoseTangentsIfNecessary(g.getMesh()); } } diff --git a/jme3-core/src/test/java/com/jme3/util/TestIssue1919.java b/jme3-core/src/test/java/com/jme3/util/TestIssue1919.java new file mode 100644 index 00000000000..87283c1c9ac --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/TestIssue1919.java @@ -0,0 +1,180 @@ +/* + * 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.util; + +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import java.nio.FloatBuffer; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies how MikktspaceTangentGenerator handles various mesh modes. This was + * issue #1919 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue1919 { + /** + * The number of axes in a vector. + */ + private static final int numAxes = 3; + + /** + * Tests a Hybrid-mode mesh. + */ + @Test(expected = UnsupportedOperationException.class) + public void testHybrid() { + Geometry testGeometry = createGeometry(Mesh.Mode.Hybrid); + MikktspaceTangentGenerator.generate(testGeometry); + } + + /** + * Tests a LineLoop-mode mesh. + */ + @Test + public void testLineLoop() { + Geometry testGeometry = createGeometry(Mesh.Mode.LineLoop); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); /// skipped this mesh + } + + /** + * Tests a LineStrip-mode mesh. + */ + @Test + public void testLineStrip() { + Geometry testGeometry = createGeometry(Mesh.Mode.LineStrip); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); /// skipped this mesh + } + + /** + * Tests a Lines-mode mesh. + */ + @Test + public void testLines() { + Geometry testGeometry = createGeometry(Mesh.Mode.Lines); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); // skipped this mesh + } + + /** + * Tests a Patch-mode mesh. + */ + @Test(expected = UnsupportedOperationException.class) + public void testPatch() { + Geometry testGeometry = createGeometry(Mesh.Mode.Patch); + MikktspaceTangentGenerator.generate(testGeometry); + } + + /** + * Tests a Points-mode mesh. + */ + @Test + public void testPoints() { + Geometry testGeometry = createGeometry(Mesh.Mode.Points); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); // skipped this mesh + } + + /** + * Tests a Triangles-mode mesh. + */ + @Test + public void testTriangles() { + Geometry testGeometry = createGeometry(Mesh.Mode.Triangles); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNotNull(tangents); // generated tangents + } + + /** + * Generates a geometry in the X-Z plane with the specified mesh mode. + * + * @param mode the desired mode (not null) + * @return a new geometry + */ + private Geometry createGeometry(Mesh.Mode mode) { + FloatBuffer normals = BufferUtils.createFloatBuffer( + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f + ); + float uvDiameter = 5f; + FloatBuffer uvs = BufferUtils.createFloatBuffer( + uvDiameter, uvDiameter, + 0f, 0f, + uvDiameter, 0f, + uvDiameter, uvDiameter, + 0f, uvDiameter, + 0f, 0f + ); + float posRadius = 500f; + FloatBuffer positions = BufferUtils.createFloatBuffer( + +posRadius, 0f, +posRadius, + -posRadius, 0f, -posRadius, + -posRadius, 0f, +posRadius, + +posRadius, 0f, +posRadius, + +posRadius, 0f, -posRadius, + -posRadius, 0f, -posRadius + ); + Mesh mesh = new Mesh(); + mesh.setMode(mode); + mesh.setBuffer(VertexBuffer.Type.Normal, numAxes, normals); + mesh.setBuffer(VertexBuffer.Type.Position, numAxes, positions); + mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); + mesh.updateBound(); + + Geometry result = new Geometry("testGeometry" + mode, mesh); + return result; + } +} From 458e9ec210c131f92f4d8e5f93f01f1dc93b0a5b Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Thu, 19 Jan 2023 09:38:20 +0330 Subject: [PATCH 085/140] main.yml: deploy with jdk17 (#1922) --- .github/workflows/main.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 360e39a8ced..b559e67f192 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -80,7 +80,7 @@ jobs: name: android-natives path: build/native - # Build the engine, we only deploy from ubuntu-latest jdk8 + # Build the engine, we only deploy from ubuntu-latest jdk17 BuildJMonkey: needs: [BuildAndroidNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} @@ -96,11 +96,13 @@ jobs: deploy: true - os: windows-2019 osName: windows + deploy: false - os: macOS-latest osName: mac - - jdk: 11 deploy: false - - jdk: 17 + - jdk: 8 + deploy: false + - jdk: 11 deploy: false steps: From c97a6d364d4a56d08ee150f8adb20646433e7c18 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 22 Jan 2023 10:02:09 +0330 Subject: [PATCH 086/140] Fix #1923 (OSSRH artifacts are build with different java version) (#1924) --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b559e67f192..6539168fa73 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -304,6 +304,13 @@ jobs: with: fetch-depth: 1 + # Setup jdk 17 used for building Sonatype OSSRH artifacts + - name: Setup the java environment + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + # Download all the stuff... - name: Download maven artifacts uses: actions/download-artifact@master From 2cc396531d17da0d97196b211beae2343c442717 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Tue, 24 Jan 2023 10:18:49 +0330 Subject: [PATCH 087/140] main.yml: build on pushes to the new v3.6 branch --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6539168fa73..b2093b603f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,7 @@ on: push: branches: - master + - v3.6 - v3.5 - v3.4 - v3.3 From ead32f180b30ecb13b881fffe96933078114251a Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Tue, 24 Jan 2023 10:42:57 +0330 Subject: [PATCH 088/140] gradle.properties: next release from "master" branch should be v3.7.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0bc60149973..3f377cdfb61 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Version number: Major.Minor.SubMinor (e.g. 3.3.0) -jmeVersion = 3.6.0 +jmeVersion = 3.7.0 # Leave empty to autogenerate # (use -PjmeVersionName="myVersion" from commandline to specify a custom version name ) From 7a5d085ff5274b651401d1460a7bf72abd006ac4 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 26 Jan 2023 22:38:06 -0800 Subject: [PATCH 089/140] resolve issue #1926 (unnecessary dependencies) (#1927) * buildscript: move def of "niftyVersion" to "common.gradle" (shared between projects) * jme3-testdata: rm dependency on "nifty-style-black" (redundant with "jme3-niftygui") * buildscript: mv "nifty-examples" dependency from "jme3-testdata" to "jme3-examples" --- common.gradle | 1 + jme3-examples/build.gradle | 1 + jme3-niftygui/build.gradle | 2 -- jme3-testdata/build.gradle | 4 ---- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/common.gradle b/common.gradle index 5961ec84b31..cadabbba0ae 100644 --- a/common.gradle +++ b/common.gradle @@ -27,6 +27,7 @@ tasks.withType(JavaCompile) { // compile-time options: ext { lwjgl3Version = '3.3.1' // 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 { diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index fbed866b7df..60912ff1a88 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -27,6 +27,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 } jar.doFirst{ diff --git a/jme3-niftygui/build.gradle b/jme3-niftygui/build.gradle index 28ed886d012..088cb562eff 100644 --- a/jme3-niftygui/build.gradle +++ b/jme3-niftygui/build.gradle @@ -1,5 +1,3 @@ -def niftyVersion = '1.4.3' - dependencies { api project(':jme3-core') api "com.github.nifty-gui:nifty:${niftyVersion}" diff --git a/jme3-testdata/build.gradle b/jme3-testdata/build.gradle index 36507063332..7d82dc72fbf 100644 --- a/jme3-testdata/build.gradle +++ b/jme3-testdata/build.gradle @@ -1,6 +1,2 @@ -def niftyVersion = '1.4.3' - dependencies { - runtimeOnly "com.github.nifty-gui:nifty-examples:$niftyVersion" - runtimeOnly "com.github.nifty-gui:nifty-style-black:$niftyVersion" } From f244e8989308107a07cd1d424136e4131e10691c Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 31 Jan 2023 10:14:29 -0800 Subject: [PATCH 090/140] solve issue #1928 (OutOfMemoryError in FBX importer) (#1929) * solve issue #1928 (OutOfMemoryError in FBX importer) * tweak the new javadoc --- .../jme3/scene/plugins/fbx/file/FbxFile.java | 31 ++++++++- .../scene/plugins/fbx/file/FbxReader.java | 66 +++++++++++++++---- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java index 9b1e6ad1b09..b1f376235fd 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,35 @@ public class FbxFile { public List rootElements = new ArrayList<>(); public long version; + /** + * Between file versions 7400 and 7500, the "endOffset", "propCount", and + * "propsLength" fields in an FBX element were extended, from 4 bytes to 8 + * bytes each. + * + * @return true for 8-byte offsets, otherwise false + */ + public boolean hasExtendedOffsets() { + if (version >= 7500L) { + return true; + } else { + return false; + } + } + + /** + * Between file versions 7400 and 7500, the FBX block sentinel was reduced, + * from 13 bytes to 9 bytes. + * + * @return the number of bytes in the block sentinel (≥0) + */ + public int numSentinelBytes() { + if (version >= 7500L) { + return 9; + } else { + return 13; + } + } + @Override public String toString() { return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]"; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java index 28b051f3766..6a916464db9 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,8 +42,6 @@ public class FbxReader { - public static final int BLOCK_SENTINEL_LENGTH = 13; - public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH]; /** * magic string at start: * "Kaydara FBX Binary\x20\x20\x00\x1a\x00" @@ -74,7 +72,7 @@ public static FbxFile readFBX(InputStream stream) throws IOException { fbxFile.version = getUInt(byteBuffer); // Read root elements while(true) { - FbxElement e = readFBXElement(byteBuffer); + FbxElement e = readFBXElement(byteBuffer, fbxFile); if(e == null) break; fbxFile.rootElements.add(e); @@ -82,13 +80,37 @@ public static FbxFile readFBX(InputStream stream) throws IOException { return fbxFile; } - private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException { + private static FbxElement readFBXElement(ByteBuffer byteBuffer, FbxFile file) + throws IOException { long endOffset = getUInt(byteBuffer); + if (file.hasExtendedOffsets()) { + long upper = getUInt(byteBuffer); + if (upper != 0L) { + throw new IOException( + "Non-zero upper bytes: 0x" + Long.toHexString(upper)); + } + } if(endOffset == 0) return null; + long propCount = getUInt(byteBuffer); + if (file.hasExtendedOffsets()) { + long upper = getUInt(byteBuffer); + if (upper != 0L) { + throw new IOException( + "Non-zero upper bytes: 0x" + Long.toHexString(upper)); + } + } + getUInt(byteBuffer); // Properties length unused - + if (file.hasExtendedOffsets()) { + long upper = getUInt(byteBuffer); + if (upper != 0L) { + throw new IOException( + "Non-zero upper bytes: 0x" + Long.toHexString(upper)); + } + } + FbxElement element = new FbxElement((int) propCount); element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer))); @@ -98,17 +120,39 @@ private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOExcepti element.propertiesTypes[i] = dataType; } if(byteBuffer.position() < endOffset) { - while(byteBuffer.position() < (endOffset - BLOCK_SENTINEL_LENGTH)) - element.children.add(readFBXElement(byteBuffer)); - - if(!Arrays.equals(BLOCK_SENTINEL_DATA, getBytes(byteBuffer, BLOCK_SENTINEL_LENGTH))) - throw new IOException("Failed to read block sentinel, expected 13 zero bytes"); + int blockSentinelLength = file.numSentinelBytes(); + while (byteBuffer.position() < (endOffset - blockSentinelLength)) { + FbxElement child = readFBXElement(byteBuffer, file); + if (child != null) { + element.children.add(child); + } + } + + if (!allZero(getBytes(byteBuffer, blockSentinelLength))) { + throw new IOException("Block sentinel is corrupt: expected all zeros."); + } } if(byteBuffer.position() != endOffset) throw new IOException("Data length not equal to expected"); return element; } + /** + * Tests whether all bytes in the specified array are zero. + * + * @param array the array to test (not null, unaffected) + * @return true if all zeroes, otherwise false + */ + private static boolean allZero(byte[] array) { + for (byte b : array) { + if (b != 0) { + return false; + } + } + + return true; + } + private static Object readData(ByteBuffer byteBuffer, char dataType) throws IOException { switch(dataType) { case 'Y': From d313a32a4c18d56a3f1d474f894b11afd3706c46 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 31 Jan 2023 10:45:05 -0800 Subject: [PATCH 091/140] solve issue #1930 (NPE in FbxLayerElement) (#1931) --- .../java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java index 6f2ba8e08a7..e8fa6403e68 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -196,7 +196,7 @@ public static FbxLayerElement fromElement(FbxElement element) { layerElement.name = (String) child.properties.get(0); } } - if (layerElement.data == null) { + if (layerElement.data == null && layerElement.dataIndices != null) { // For Smoothing / Materials, data = dataIndices layerElement.refInfoType = ReferenceInformationType.Direct; layerElement.data = new Integer[layerElement.dataIndices.length]; From c0efec4c7f53b085e9ad517f50e80a769c527a47 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 31 Jan 2023 11:09:40 -0800 Subject: [PATCH 092/140] solve issue #1932 (class cast exceptions in FBX importer) (#1934) --- .../scene/plugins/fbx/anim/FbxBindPose.java | 49 ++++++++++++++----- .../scene/plugins/fbx/anim/FbxCluster.java | 40 +++++++++++++-- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java index cd701e5066b..45b71b224a7 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,18 +62,43 @@ public void fromElement(FbxElement element) { if (e.id.equals("Node")) { node = FbxId.create(e.properties.get(0)); } else if (e.id.equals("Matrix")) { - double[] matDataDoubles = (double[]) e.properties.get(0); - - if (matDataDoubles.length != 16) { - // corrupt - throw new UnsupportedOperationException("Bind pose matrix " - + "must have 16 doubles, but it has " - + matDataDoubles.length + ". Data is corrupt"); - } - matData = new float[16]; - for (int i = 0; i < matDataDoubles.length; i++) { - matData[i] = (float) matDataDoubles[i]; + int numProperties = e.propertiesTypes.length; + if (numProperties == 1) { + char propertyType = e.propertiesTypes[0]; + if (propertyType != 'd') { + throw new UnsupportedOperationException( + "Bind-pose matrix should have property type 'd'," + + "but found '" + propertyType + "'"); + } + double[] array = (double[]) e.properties.get(0); + int length = array.length; + if (length != 16) { + throw new UnsupportedOperationException( + "Bind-pose matrix should have 16 elements," + + "but found " + length); + } + for (int i = 0; i < length; ++i) { + matData[i] = (float) array[i]; + } + + } else if (numProperties == 16) { + for (int i = 0; i < numProperties; ++i) { + char propertyType = e.propertiesTypes[i]; + if (propertyType != 'D') { + throw new UnsupportedOperationException( + "Bind-pose matrix should have properties of type 'D'," + + "but found '" + propertyType + "'"); + } + double d = (Double) e.properties.get(i); + matData[i] = (float) d; + } + + } else { + throw new UnsupportedOperationException( + "Bind pose matrix should have either " + + "1 or 16 properties, but found " + + numProperties); } } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java index 3065ce88c3e..45801fc3533 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,9 +54,43 @@ public void fromElement(FbxElement element) { super.fromElement(element); for (FbxElement e : element.children) { if (e.id.equals("Indexes")) { - indexes = (int[]) e.properties.get(0); + int numProperties = e.propertiesTypes.length; + if (numProperties == 1 && e.propertiesTypes[0] == 'i') { + this.indexes = (int[]) e.properties.get(0); + + } else { + this.indexes = new int[numProperties]; + for (int i = 0; i < numProperties; ++i) { + char propertyType = e.propertiesTypes[i]; + if (propertyType != 'I') { + throw new UnsupportedOperationException( + "Indexes should have properties of type 'I'," + + "but found '" + propertyType + "'"); + } + int index = (Integer) e.properties.get(i); + this.indexes[i] = index; + } + } + } else if (e.id.equals("Weights")) { - weights = (double[]) e.properties.get(0); + int numTypes = e.propertiesTypes.length; + if (numTypes == 1 && e.propertiesTypes[0] == 'd') { + this.weights = (double[]) e.properties.get(0); + + } else { + int numElements = numTypes; + this.weights = new double[numElements]; + for (int i = 0; i < numElements; ++i) { + int propertyType = e.propertiesTypes[i]; + if (propertyType != 'D') { + throw new UnsupportedOperationException( + "Weights should have properties of type 'D'," + + "but found '" + propertyType + "'"); + } + double weight = (Double) e.properties.get(i); + this.weights[i] = weight; + } + } } } } From 08d6984b3eb2289c23dee600a2f1de6ef2c59db0 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 31 Jan 2023 11:28:22 -0800 Subject: [PATCH 093/140] solve issue #1933 (unsupported operation in FbxNode) (#1936) --- .../src/main/java/com/jme3/math/Matrix4f.java | 71 ++++++++++++++++++- .../jme3/scene/plugins/fbx/node/FbxNode.java | 4 +- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java index 955a4d50fbf..6c6e7449e22 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -2460,6 +2460,75 @@ public void multLocal(Quaternion rotation) { multLocal(matrix4f); } + /** + * Tests for approximate equality with the specified matrix, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param other the matrix to compare (unaffected) or null for none + * @param epsilon the tolerance for each element + * @return true if all 16 elements are within tolerance, otherwise false + */ + public boolean isSimilar(Matrix4f other, float epsilon) { + if (other == null) { + return false; + } + + if (Float.compare(Math.abs(other.m00 - m00), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m01 - m01), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m02 - m02), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m03 - m03), epsilon) > 0) { + return false; + } + + if (Float.compare(Math.abs(other.m10 - m10), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m11 - m11), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m12 - m12), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m13 - m13), epsilon) > 0) { + return false; + } + + if (Float.compare(Math.abs(other.m20 - m20), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m21 - m21), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m22 - m22), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m23 - m23), epsilon) > 0) { + return false; + } + + if (Float.compare(Math.abs(other.m30 - m30), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m31 - m31), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m32 - m32), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m33 - m33), epsilon) > 0) { + return false; + } + + return true; + } + /** * Creates a copy. The current instance is unaffected. * diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java index 8e3d9c5324f..678e88eae9e 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -159,7 +159,7 @@ public Transform computeFbxLocalTransform() { public void setWorldBindPose(Matrix4f worldBindPose) { if (cachedWorldBindPose != null) { - if (!cachedWorldBindPose.equals(worldBindPose)) { + if (!cachedWorldBindPose.isSimilar(worldBindPose, 1e-6f)) { throw new UnsupportedOperationException("Bind poses don't match"); } } From 18fa6bed8cd37d063da52360d8f01175e6d1f5dd Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 31 Jan 2023 12:02:41 -0800 Subject: [PATCH 094/140] solve issue #1937 (NPE in FbxObject) (#1938) --- .../com/jme3/scene/plugins/fbx/FbxLoader.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java index 7a0bf1d58a0..d0816dc1844 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -257,8 +257,23 @@ private void connectObjects(FbxElement element) { parentId = FbxId.create(el.properties.get(2)); String propName = (String) el.properties.get(3); FbxObject child = objectMap.get(childId); + if (child == null) { + logger.log(Level.WARNING, + "Missing child object with ID {0}. Skipping object-" + + "property connection for property \"{1}\"", + new Object[]{childId, propName}); + } FbxObject parent = objectMap.get(parentId); - parent.connectObjectProperty(child, propName); + if (parent == null) { + logger.log(Level.WARNING, + "Missing parent object with ID {0}. Skipping object-" + + "property connection for property \"{1}\"", + new Object[]{parentId, propName}); + } + if (parent != null && child != null) { + parent.connectObjectProperty(child, propName); + } + } else { logger.log(Level.WARNING, "Unknown connection type: {0}. Ignoring.", type); } From aaabd104771019e1a627696a98f5de5794c3c4cc Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 3 Feb 2023 10:56:05 -0800 Subject: [PATCH 095/140] solve issue #1939 [NPE in FbxMesh.applyCluster()] (#1940) * FBXCluster: create empty arrays if the cluster contains no keyframes * FbxLoader: don't construct an animation if there are no keyframes --- .../com/jme3/scene/plugins/fbx/FbxLoader.java | 3 +++ .../scene/plugins/fbx/anim/FbxCluster.java | 8 +++++++- .../scene/plugins/fbx/anim/FbxToJmeTrack.java | 20 +++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java index d0816dc1844..02413e601f3 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java @@ -343,6 +343,9 @@ private void constructAnimations() { // At this point we can construct the animation for all pairs ... for (FbxToJmeTrack pair : pairs.values()) { + if (pair.countKeyframes() == 0) { + continue; + } String animName = pair.animStack.getName(); float duration = pair.animStack.getDuration(); diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java index 45801fc3533..b010e66a92c 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java @@ -93,6 +93,12 @@ public void fromElement(FbxElement element) { } } } + + if (indexes == null && weights == null) { + // The cluster doesn't contain any keyframes! + this.indexes = new int[0]; + this.weights = new double[0]; + } } public int[] getVertexIndices() { @@ -129,4 +135,4 @@ public void connectObject(FbxObject object) { public void connectObjectProperty(FbxObject object, String property) { unsupportedConnectObjectProperty(object, property); } -} \ No newline at end of file +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java index 2e302fc4d3f..4ae677700f9 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,7 +91,23 @@ public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) { public SpatialTrack toJmeSpatialTrack() { return (SpatialTrack) toJmeTrackInternal(-1, null); } - + + /** + * Counts how many keyframes there are in the included curves. + * + * @return the total number of keyframes (≥0) + */ + public int countKeyframes() { + int count = 0; + for (FbxAnimCurveNode curveNode : animCurves.values()) { + for (FbxAnimCurve curve : curveNode.getCurves()) { + count += curve.getKeyTimes().length; + } + } + + return count; + } + public float getDuration() { long[] keyframes = getKeyTimes(); return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT); From 3d203a16dfba741a7b6e8566f6ee465c2cb3dc89 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 10 Feb 2023 10:19:35 -0800 Subject: [PATCH 096/140] update Groovy to v3.0.15 --- common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.gradle b/common.gradle index cadabbba0ae..09dceeae4d0 100644 --- a/common.gradle +++ b/common.gradle @@ -41,7 +41,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.14' + testImplementation 'org.codehaus.groovy:groovy-test:3.0.15' } // Uncomment if you want to see the status of every test that is run and From 8edb89a22963e4bf789df1531ae3014a158cb837 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 12 Feb 2023 09:15:35 +0330 Subject: [PATCH 097/140] Update Application.start javadoc. (#1947) --- jme3-core/src/main/java/com/jme3/app/Application.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/app/Application.java b/jme3-core/src/main/java/com/jme3/app/Application.java index f9ddc926574..198d730efa2 100644 --- a/jme3-core/src/main/java/com/jme3/app/Application.java +++ b/jme3-core/src/main/java/com/jme3/app/Application.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -169,13 +169,15 @@ public interface Application { /** * Starts the application. - * A bug occurring when using LWJGL3 prevents this method from returning until after the application is stopped. + * A bug occurring when using LWJGL3 prevents this method from + * returning until after the application is stopped on macOS. */ public void start(); /** * Starts the application. - * A bug occurring when using LWJGL3 prevents this method from returning until after the application is stopped. + * A bug occurring when using LWJGL3 prevents this method from + * returning until after the application is stopped on macOS. * * @param waitFor true→wait for the context to be initialized, * false→don't wait From fec08b3f60fa17e88d4a7ac419c7a0c8b2b0c5d9 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 12 Feb 2023 09:16:43 +0330 Subject: [PATCH 098/140] Update RenderState.setLineWidth javadoc. (#1948) --- jme3-core/src/main/java/com/jme3/material/RenderState.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java index 8ab30431847..e0be1f0563d 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -983,6 +983,8 @@ public void setDepthFunc(TestFunction depthFunc) { * Sets the mesh line width. * Use this in conjunction with {@link #setWireframe(boolean)} or with a mesh in * {@link com.jme3.scene.Mesh.Mode#Lines} mode. + * Note: this does not work in OpenGL core profile. It only works in + * compatibility profile. * * @param lineWidth the line width. */ From 4d407b05e8f1f4e67f146e6bb502b1e5db93fb4a Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 12 Feb 2023 09:19:18 +0330 Subject: [PATCH 099/140] Fix #1945 (IllegalStateException when running TestAWTPanels with LWJGL 3) (#1949) --- .../main/java/com/jme3/system/awt/AwtPanelsContext.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java index a4132510995..032c8457eba 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -41,9 +41,12 @@ import com.jme3.renderer.Renderer; import com.jme3.system.*; import java.util.ArrayList; +import java.util.logging.Logger; public class AwtPanelsContext implements JmeContext { + private static final Logger logger = Logger.getLogger(AwtPanelsContext.class.getName()); + protected JmeContext actualContext; protected AppSettings settings = new AppSettings(true); protected SystemListener listener; @@ -64,12 +67,12 @@ public void initialize() { @Override public void reshape(int width, int height) { - throw new IllegalStateException(); + logger.severe("reshape is not supported."); } @Override public void rescale(float x, float y) { - throw new IllegalStateException(); + logger.severe("rescale is not supported."); } @Override From 04d815549ad99903bd89a4489c1b4faab3120b7d Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 11 Feb 2023 21:50:10 -0800 Subject: [PATCH 100/140] solve issue #1879 (compile-time error in Skinning.glsllib) (#1942) --- .../src/main/resources/Common/ShaderLib/Skinning.glsllib | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib index 293879159ef..0e0696c7a32 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib @@ -50,10 +50,12 @@ void Skinning_Compute(inout vec4 position, inout vec3 normal, inout vec3 tangent if (inHWBoneWeight.x != 0.0) { #if NUM_WEIGHTS_PER_VERT == 1 position = m_BoneMatrices[int(inHWBoneIndex.x)] * position; - tangent = m_BoneMatrices[int(inHWBoneIndex.x)] * tangent; - normal = (mat3(m_BoneMatrices[int(inHWBoneIndex.x)][0].xyz, + + mat3 rotMat = mat3(m_BoneMatrices[int(inHWBoneIndex.x)][0].xyz, m_BoneMatrices[int(inHWBoneIndex.x)][1].xyz, - m_BoneMatrices[int(inHWBoneIndex.x)][2].xyz) * normal); + m_BoneMatrices[int(inHWBoneIndex.x)][2].xyz); + tangent = rotMat * tangent; + normal = rotMat * normal; #else mat4 mat = mat4(0.0); mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x; From 634462de478e3c7beb0f8dfab412bd7b6db109ed Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sun, 12 Feb 2023 09:22:54 +0330 Subject: [PATCH 101/140] Fix issue #1558 (TestAWTPanels crashes with LWJGL v3 on Linux) (#1944) * TestAwtPanels: apply swing system LAF. This is needed to prevent JVM crash on Linux and LWJGL 3. (See issue #1558) * Use single class imports. --- .../main/java/jme3test/awt/TestAwtPanels.java | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java b/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java index d8c6aa8c92f..9dab1090218 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-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 jme3test.awt; import com.jme3.app.SimpleApplication; @@ -18,9 +49,12 @@ import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.SwingUtilities; +import javax.swing.UIManager; public class TestAwtPanels extends SimpleApplication { + private static final Logger logger = Logger.getLogger(TestAwtPanels.class.getName()); + final private static CountDownLatch panelsAreReady = new CountDownLatch(1); private static TestAwtPanels app; private static AwtPanel panel, panel2; @@ -46,7 +80,13 @@ public void windowClosed(WindowEvent e) { public static void main(String[] args){ Logger.getLogger("com.jme3").setLevel(Level.WARNING); - + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + logger.warning("Could not set native look and feel."); + } + app = new TestAwtPanels(); app.setShowSettings(false); AppSettings settings = new AppSettings(true); From e7eb4d87739003465c89d79e595563d9a127e30f Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 11 Feb 2023 23:37:25 -0800 Subject: [PATCH 102/140] solve issue #1806 (global FrameInterpolator violates threading model) (#1943) * solve issue #1806 (global FrameInterpolator violates threading model) * FrameInterpolator: deprecate the global instance --- .../main/java/com/jme3/anim/MorphTrack.java | 12 +++++++--- .../java/com/jme3/anim/TransformTrack.java | 13 +++++++--- .../anim/interpolator/FrameInterpolator.java | 24 ++++++++++++++++++- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java index e4c6fdd233b..ee767d28858 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,11 @@ public class MorphTrack implements AnimTrack { * Weights and times for track. */ private float[] weights; - private FrameInterpolator interpolator = FrameInterpolator.DEFAULT; + /** + * The interpolator to use, or null to always use the default interpolator + * of the current thread. + */ + private FrameInterpolator interpolator = null; private float[] times; private int nbMorphTargets; @@ -219,7 +223,9 @@ public void getDataAtTime(double t, float[] store) { / (times[endFrame] - times[startFrame]); } - interpolator.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store); + FrameInterpolator fi = (interpolator == null) + ? FrameInterpolator.getThreadDefault() : interpolator; + fi.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store); } /** diff --git a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java index b226c6d7a52..3492d17eec1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,7 +50,11 @@ public class TransformTrack implements AnimTrack { private double length; - private FrameInterpolator interpolator = FrameInterpolator.DEFAULT; + /** + * The interpolator to use, or null to always use the default interpolator + * of the current thread. + */ + private FrameInterpolator interpolator = null; private HasLocalTransform target; /** @@ -281,7 +285,10 @@ public void getDataAtTime(double t, Transform transform) { / (times[endFrame] - times[startFrame]); } - Transform interpolated = interpolator.interpolate(blend, startFrame, translations, rotations, scales, times); + FrameInterpolator fi = (interpolator == null) + ? FrameInterpolator.getThreadDefault() : interpolator; + Transform interpolated = fi.interpolate( + blend, startFrame, translations, rotations, scales, times); if (translations != null) { transform.setTranslation(interpolated.getTranslation()); diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java index 89cde24dda5..13c7d2f1836 100644 --- a/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,19 @@ * Created by nehon on 15/04/17. */ public class FrameInterpolator { + /** + * A global default instance of this class, for compatibility with JME v3.5. + * Due to issue #1806, use of this instance is discouraged. + * + * @deprecated use {@link #getThreadDefault()} + */ + @Deprecated public static final FrameInterpolator DEFAULT = new FrameInterpolator(); + /** + * The per-thread default instances of this class. + */ + private static final ThreadLocal THREAD_DEFAULT + = ThreadLocal.withInitial(() -> new FrameInterpolator()); private AnimInterpolator timeInterpolator; private AnimInterpolator translationInterpolator = AnimInterpolators.LinearVec3f; @@ -52,6 +64,16 @@ public class FrameInterpolator { final private Transform transforms = new Transform(); + /** + * Obtain the default interpolator for the current thread. + * + * @return the pre-existing instance (not null) + */ + public static FrameInterpolator getThreadDefault() { + FrameInterpolator result = THREAD_DEFAULT.get(); + return result; + } + public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, CompactQuaternionArray rotations, CompactVector3Array scales, float[] times) { timesReader.setData(times); From 5b44743cbe2cc00439e90a6e52c3098e6b07d088 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Mon, 13 Feb 2023 08:20:08 +0330 Subject: [PATCH 103/140] Fix a typo in LwjglWindow (#1953) --- .../src/main/java/com/jme3/system/lwjgl/LwjglWindow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b0156d83323..1647540ffbe 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 @@ -287,7 +287,7 @@ public void invoke(int error, long description) { final GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); int requestWidth = settings.getWindowWidth(); int requestHeight = settings.getWindowHeight(); - if (requestWidth <= 0 || requestWidth <= 0) { + if (requestWidth <= 0 || requestHeight <= 0) { requestWidth = videoMode.width(); requestHeight = videoMode.height(); } From 493195eae810c4a38744e6c34ea0e7e8c48542a9 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Mon, 13 Feb 2023 08:21:30 +0330 Subject: [PATCH 104/140] Add WaterFilter.getReflectionView method (#1951) * Added WaterFilter.getReflectionView method. * Update javadoc. --- .../src/main/java/com/jme3/water/WaterFilter.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java index be110935d37..509cbd0ed0f 100644 --- a/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java +++ b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -516,6 +516,15 @@ public Spatial getReflectionScene() { return reflectionScene; } + /** + * Gets the view port used to render reflection scene. + * + * @return the reflection view port. + */ + public ViewPort getReflectionView() { + return reflectionView; + } + /** * returns the waterTransparency value * @return the transparency value From 2bf63e9352a6f55bc6227b7c1e97f183341345f1 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sun, 19 Feb 2023 10:02:43 -0800 Subject: [PATCH 105/140] README.md: delete a dead link (Maker's Tale) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7c26bd10033..933c8cd67f7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ 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) - - [Maker's Tale](http://steamcommunity.com/sharedfiles/filedetails/?id=93461954t) - [Boardtastic 2](https://boardtastic-2.fileplanet.com/apk) - [Attack of the Gelatinous Blob](https://attack-gelatinous-blob.softwareandgames.com/) - [Mythruna](http://mythruna.com/) From 292bd4c436028841259ed541b84afe52a5fa50c1 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sun, 19 Feb 2023 10:03:18 -0800 Subject: [PATCH 106/140] README.md: add a link to Wild Magic --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 933c8cd67f7..420813dfb35 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The engine is used by several commercial game studios and computer-science cours - [Chatter Games](https://chatter-games.com) - [Exotic Matter](https://exoticmatter.io) - [Demon Lord](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1) + - [Wild Magic](http://wildmagicgame.ru/) ## Getting started From 3d49531fb98c8e41bf39d6a2167d5c3b95557148 Mon Sep 17 00:00:00 2001 From: pavl_g <60224159+Scrappers-glitch@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:02:47 +0200 Subject: [PATCH 107/140] resolve issue #1955 (Can not play vorbis audio on Android API 31+) (#1956) * android-native-vorbis: fix double asset file descriptor closure * NativeVorbis: better names and javadocs * NativeVorbisFile#readIntoBuffer: specifies the start and the end of the read * NativeVorbisFile: better explanation for the output buffer on read functions * com_jme3_audio_plugins_NativeVorbisFile.c: refactored logs * NativeVorbisFile: some docs enhances * NativeVorbisLoader: added updated jme3-copyright --- .../com_jme3_audio_plugins_NativeVorbisFile.c | 45 ++++---- .../jme3/audio/plugins/NativeVorbisFile.java | 105 ++++++++++++++++-- .../audio/plugins/NativeVorbisLoader.java | 43 ++++++- 3 files changed, 158 insertions(+), 35 deletions(-) diff --git a/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c b/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c index ceebbd81ba6..25a15f5b8a4 100644 --- a/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c +++ b/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c @@ -110,13 +110,18 @@ static int FileDesc_seek(void *datasource, ogg_int64_t offset, int whence) wrapper->current = actual_offset; } -static int FileDesc_close(void *datasource) +static int FileDesc_clear(void *datasource) { FileDescWrapper* wrapper = (FileDescWrapper*)datasource; - LOGI("FD close"); - - return close(wrapper->fd); + LOGI("Clear resources -- delegating closure to the Android ParcelFileDescriptor"); + + /* release the file descriptor wrapper buffer */ + free(wrapper); + + wrapper = NULL; + + return 0; } static long FileDesc_tell(void *datasource) @@ -139,7 +144,7 @@ static long FileDesc_tell(void *datasource) static ov_callbacks FileDescCallbacks = { FileDesc_read, FileDesc_seek, - FileDesc_close, + FileDesc_clear, FileDesc_tell }; @@ -157,10 +162,10 @@ static jfieldID nvf_field_bitRate; static jfieldID nvf_field_totalBytes; static jfieldID nvf_field_duration; -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_nativeInit +JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_preInit (JNIEnv *env, jclass clazz) { - LOGI("nativeInit"); + LOGI("preInit"); nvf_field_ovf = (*env)->GetFieldID(env, clazz, "ovf", "Ljava/nio/ByteBuffer;");; nvf_field_seekable = (*env)->GetFieldID(env, clazz, "seekable", "Z"); @@ -171,10 +176,10 @@ JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_nativeInit nvf_field_duration = (*env)->GetFieldID(env, clazz, "duration", "F"); } -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_open +JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_init (JNIEnv *env, jobject nvf, jint fd, jlong off, jlong len) { - LOGI("open: fd = %d, off = %lld, len = %lld", fd, off, len); + LOGI("init: fd = %d, off = %lld, len = %lld", fd, off, len); OggVorbis_File* ovf = (OggVorbis_File*) malloc(sizeof(OggVorbis_File)); @@ -189,19 +194,19 @@ JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_open if (result != 0) { - LOGI("ov_open fail"); + LOGI("init fail"); free(ovf); free(wrapper); char err[512]; - sprintf(err, "ov_open failed: %d", result); + sprintf(err, "init failed: %d", result); throwIOException(env, err); return; } - LOGI("ov_open OK"); + LOGI("init OK"); jobject ovfBuf = (*env)->NewDirectByteBuffer(env, ovf, sizeof(OggVorbis_File)); vorbis_info* info = ov_info(ovf, -1); @@ -246,7 +251,7 @@ JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_seekTime } } -JNIEXPORT jint JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_read +JNIEXPORT jint JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readIntoArray (JNIEnv *env, jobject nvf, jbyteArray buf, jint off, jint len) { int bitstream = -1; @@ -288,7 +293,7 @@ JNIEXPORT jint JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_read return result; } -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readFully +JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readIntoBuffer (JNIEnv *env, jobject nvf, jobject buf) { int bitstream = -1; @@ -330,19 +335,19 @@ JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readFully } } -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_close +JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_clearResources (JNIEnv *env, jobject nvf) { - LOGI("close"); + LOGI("clearResources"); jobject ovfBuf = (*env)->GetObjectField(env, nvf, nvf_field_ovf); OggVorbis_File* ovf = (OggVorbis_File*) (*env)->GetDirectBufferAddress(env, ovfBuf); - FileDescWrapper* wrapper = (FileDescWrapper*) ovf->datasource; - wrapper->env = env; + /* release the ovf resources */ ov_clear(ovf); - - free(wrapper); + /* release the ovf buffer */ free(ovf); + ovf = NULL; + /* destroy the java reference object */ (*env)->SetObjectField(env, nvf, nvf_field_ovf, NULL); } diff --git a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java index bd6ecc62737..9af1e62cbe2 100644 --- a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java +++ b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java @@ -1,8 +1,49 @@ +/* + * Copyright (c) 2009-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.audio.plugins; import java.io.IOException; import java.nio.ByteBuffer; +/** + * Represents the android implementation for the native vorbis file decoder. + * This decoder initializes an OggVorbis_File from an already opened file designated by the {@link NativeVorbisFile#fd}. + *
+ * + * Code by Kirill Vainer. + *
+ * + * Modified by pavl_g. + */ public class NativeVorbisFile { public int fd; @@ -16,22 +57,68 @@ public class NativeVorbisFile { static { System.loadLibrary("decodejme"); - nativeInit(); + preInit(); } - public NativeVorbisFile(int fd, long off, long len) throws IOException { - open(fd, off, len); + /** + * Initializes an ogg vorbis native file from a file descriptor [fd] of an already opened file. + * + * @param fd an integer representing the file descriptor + * @param offset an integer indicating the start of the buffer + * @param length an integer indicating the end of the buffer + * @throws IOException in cases of a failure to initialize the vorbis file + */ + public NativeVorbisFile(int fd, long offset, long length) throws IOException { + init(fd, offset, length); } - private native void open(int fd, long off, long len) throws IOException; - + /** + * Seeks to a playback time relative to the decompressed pcm (Pulse-code modulation) stream. + * + * @param time the playback seek time + * @throws IOException if the seek is not successful + */ public native void seekTime(double time) throws IOException; - public native int read(byte[] buf, int off, int len) throws IOException; + /** + * Reads the vorbis file into a primitive byte buffer [buf] with an [offset] indicating the start byte and a [length] indicating the end byte on the output buffer. + * + * @param buffer a primitive byte buffer to read the data into it + * @param offset an integer representing the offset or the start byte on the output buffer + * @param length an integer representing the end byte on the output buffer + * @return the number of the read bytes, (-1) if the reading has failed indicating an EOF, + * returns (0) if the reading has failed or the primitive [buffer] passed is null + * @throws IOException if the library has failed to read the file into the [out] buffer + * or if the java primitive byte array [buffer] is inaccessible + */ + public native int readIntoArray(byte[] buffer, int offset, int length) throws IOException; + + /** + * Reads the vorbis file into a direct {@link java.nio.ByteBuffer}, starting from offset [0] till the buffer end on the output buffer. + * + * @param out a reference to the output direct buffer + * @throws IOException if a premature EOF is encountered before reaching the end of the buffer + * or if the library has failed to read the file into the [out] buffer + */ + public native void readIntoBuffer(ByteBuffer out) throws IOException; - public native void readFully(ByteBuffer out) throws IOException; + /** + * Clears the native resources and destroys the buffer {@link NativeVorbisFile#ovf} reference. + */ + public native void clearResources(); - public native void close(); + /** + * Prepares the java fields for the native environment. + */ + private static native void preInit(); - public static native void nativeInit(); + /** + * Initializes an ogg vorbis native file from a file descriptor [fd] of an already opened file. + * + * @param fd an integer representing the file descriptor + * @param offset an integer representing the start of the buffer + * @param length an integer representing the length of the buffer + * @throws IOException in cases of a failure to initialize the vorbis file + */ + private native void init(int fd, long offset, long length) throws IOException; } diff --git a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java index 3093b0d4b20..63517cd3d84 100644 --- a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java +++ b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-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.audio.plugins; import android.content.res.AssetFileDescriptor; @@ -33,12 +64,12 @@ public int read() throws IOException { @Override public int read(byte[] buf) throws IOException { - return file.read(buf, 0, buf.length); + return file.readIntoArray(buf, 0, buf.length); } @Override public int read(byte[] buf, int off, int len) throws IOException { - return file.read(buf, off, len); + return file.readIntoArray(buf, off, len); } @Override @@ -57,7 +88,7 @@ public void setTime(float time) { @Override public void close() throws IOException { - file.close(); + file.clearResources(); afd.close(); } } @@ -71,14 +102,14 @@ private static AudioBuffer loadBuffer(AssetInfo assetInfo) throws IOException { int fd = afd.getParcelFileDescriptor().getFd(); file = new NativeVorbisFile(fd, afd.getStartOffset(), afd.getLength()); ByteBuffer data = BufferUtils.createByteBuffer(file.totalBytes); - file.readFully(data); + file.readIntoBuffer(data); AudioBuffer ab = new AudioBuffer(); ab.setupFormat(file.channels, 16, file.sampleRate); ab.updateData(data); return ab; } finally { if (file != null) { - file.close(); + file.clearResources(); } if (afd != null) { afd.close(); @@ -107,7 +138,7 @@ private static AudioStream loadStream(AssetInfo assetInfo) throws IOException { } finally { if (!success) { if (file != null) { - file.close(); + file.clearResources(); } if (afd != null) { afd.close(); From 0cb5fe755582d776ddf223ce88771d958f4818cf Mon Sep 17 00:00:00 2001 From: Github Actions Date: Tue, 21 Feb 2023 12:19:37 +0000 Subject: [PATCH 108/140] [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 420ae3baa11..2e7a470a184 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=1ba207197973dacb79cc22b278f634d3d06823e0 +natives.snapshot=3d49531fb98c8e41bf39d6a2167d5c3b95557148 From 1cb03bb07653409be9f3a762928aa2dc3f4df927 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Tue, 21 Feb 2023 23:15:26 -0800 Subject: [PATCH 109/140] jme3-jogg: remove dependency on Java Media Framework (#1962) --- jme3-jogg/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-jogg/build.gradle b/jme3-jogg/build.gradle index c8cc0178e1d..a039b96bcaf 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-all:1.0.2' + api 'com.github.stephengold:j-ogg-vorbis:1.0.3' } From 375015f14c5f3cbda18302b3aa990e5f023e9da1 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Wed, 22 Feb 2023 11:19:24 +0330 Subject: [PATCH 110/140] solve issue #1960 (use jme3-jogg for loading ogg files on android) (#1961) --- jme3-android/src/main/resources/com/jme3/asset/Android.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-android/src/main/resources/com/jme3/asset/Android.cfg b/jme3-android/src/main/resources/com/jme3/asset/Android.cfg index 6ebf2f945ff..7234abfaa75 100644 --- a/jme3-android/src/main/resources/com/jme3/asset/Android.cfg +++ b/jme3-android/src/main/resources/com/jme3/asset/Android.cfg @@ -6,4 +6,4 @@ LOCATOR / com.jme3.asset.plugins.AndroidLocator # Android specific loaders LOADER com.jme3.texture.plugins.AndroidNativeImageLoader : jpg, bmp, gif, png, jpeg LOADER com.jme3.texture.plugins.AndroidBufferImageLoader : webp, heic, heif -LOADER com.jme3.audio.plugins.NativeVorbisLoader : ogg +LOADER com.jme3.audio.plugins.OGGLoader : ogg From 8867934fddcf577dd75b8db02f2b3f071e6a8513 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Fri, 24 Feb 2023 07:41:17 +0330 Subject: [PATCH 111/140] solve issue #1963 (TestMusicPlayer fails to load AL library on lwjgl2) (#1964) * TestMusicPlayer: fix UnsatisfiedLinkError when using lwjgl2. Solves issue #1963 * Add missing load function for "openal" natives. * Removed the unnecessary check for lwjgl 2 in classpath. * Fix comment --- .../main/java/jme3test/audio/TestMusicPlayer.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java index 1f1909292af..777ed656ace 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,8 @@ import com.jme3.audio.plugins.WAVLoader; import com.jme3.system.AppSettings; import com.jme3.system.JmeSystem; +import com.jme3.system.NativeLibraryLoader; + import java.io.*; import javax.swing.JFileChooser; @@ -52,6 +54,17 @@ public class TestMusicPlayer extends javax.swing.JFrame { private float curTime = 0; final private Listener listener = new Listener(); + static { + // Load lwjgl and openal natives if lwjgl version 2 is in classpath. + // + // In case of lwjgl 2, natives are loaded when LwjglContext is + // started, but in this test we do not create a LwjglContext, + // so we should handle loading natives ourselves if running + // with lwjgl 2. + NativeLibraryLoader.loadNativeLibrary("lwjgl", false); + NativeLibraryLoader.loadNativeLibrary("openal", false); + } + public TestMusicPlayer() { initComponents(); setLocationRelativeTo(null); From 3f1c24e7fd748a9ccabd288bbc964f1edb1c0de0 Mon Sep 17 00:00:00 2001 From: Ryan McDonough Date: Sat, 25 Feb 2023 00:40:39 -0500 Subject: [PATCH 112/140] Replace Exception with warning in TerrainPatch (#1966) * Replace Excpetion with warning in TerrainPatch PR following up on the discussion in this thread: https://hub.jmonkeyengine.org/t/terrain-collision-exception/46491/6 * Update TerrainPatch.java * Update TerrainPatch.java * Update TerrainPatch.java * Update TerrainPatch.java * Update TerrainPatch.java * Update TerrainPatch.java * Update TerrainPatch.java --- .../jme3/terrain/geomipmap/TerrainPatch.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java index a120120602c..b4ffc5a12d9 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,6 +56,8 @@ import java.nio.FloatBuffer; import java.util.HashMap; import java.util.List; +import java.util.logging.Logger; +import java.util.logging.Level; /** @@ -79,6 +81,8 @@ * @author Brent Owens */ public class TerrainPatch extends Geometry { + + private static final Logger logger = Logger.getLogger(TerrainPatch.class.getName()); protected LODGeomap geomap; protected int lod = 0; // this terrain patch's LOD @@ -798,19 +802,22 @@ protected void setLodBottom(int lodBottom) { @Override public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { - if (refreshFlags != 0) - throw new IllegalStateException("Scene graph must be updated" + - " before checking collision"); - - if (other instanceof BoundingVolume) - if (!getWorldBound().intersects((BoundingVolume)other)) + if ((refreshFlags & (RF_BOUND | RF_TRANSFORM)) != 0) { + logger.log(Level.WARNING, "Scene graph must be updated before checking collision"); + return 0; + } + + if (other instanceof BoundingVolume) { + if (!getWorldBound().intersects((BoundingVolume)other)) { return 0; - - if(other instanceof Ray) + } + } + + if (other instanceof Ray) { return collideWithRay((Ray)other, results); - else if (other instanceof BoundingVolume) + } else if (other instanceof BoundingVolume) { return collideWithBoundingVolume((BoundingVolume)other, results); - else { + } else { throw new UnsupportedCollisionException("TerrainPatch cannot collide with "+other.getClass().getName()); } } From 5e859d59a3193608aa2f55e582515cd3ae8192b7 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Sat, 25 Feb 2023 09:14:05 +0330 Subject: [PATCH 113/140] Cleanup NativeLibraryLoader & fix wrong library path (#1967) * Cleanup NativeLibraryLoader * Fix wrong library path --- .../com/jme3/system/NativeLibraryLoader.java | 95 ++++++------------- .../com/jme3/system/lwjgl/LwjglContextVR.java | 8 -- 2 files changed, 30 insertions(+), 73 deletions(-) diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index d6bb9e6c11e..8c5c790be28 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -75,7 +75,7 @@ public final class NativeLibraryLoader { private static File extractionFolder = null; private static final HashMap nativeLibraryMap - = new HashMap(); + = new HashMap<>(); /** * Register a new known library. @@ -120,55 +120,25 @@ public static void registerNativeLibrary(String name, Platform platform, } static { - // LWJGL - registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl.dll"); - registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl64.dll"); - registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl.so"); - registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl64.so"); - registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib"); - registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib"); + // Note: LWJGL 3 handles its native library extracting & loading using + // its own SharedLibraryLoader. - // OpenAL - // For OSX: Need to add lib prefix when extracting - registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll"); - registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL64.dll"); - registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal.so"); - registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal64.so"); - registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib"); - registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib"); - - // LWJGL 3.x - registerNativeLibrary("lwjgl3", Platform.Windows32, "native/windows/lwjgl32.dll"); - registerNativeLibrary("lwjgl3", Platform.Windows64, "native/windows/lwjgl.dll"); - registerNativeLibrary("lwjgl3", Platform.Linux32, "native/linux/liblwjgl32.so"); - registerNativeLibrary("lwjgl3", Platform.Linux64, "native/linux/liblwjgl.so"); - registerNativeLibrary("lwjgl3", Platform.MacOSX32, "native/macosx/liblwjgl.dylib"); - registerNativeLibrary("lwjgl3", Platform.MacOSX64, "native/macosx/liblwjgl.dylib"); - - // GLFW for LWJGL 3.x - registerNativeLibrary("glfw-lwjgl3", Platform.Windows32, "native/windows/glfw32.dll"); - registerNativeLibrary("glfw-lwjgl3", Platform.Windows64, "native/windows/glfw.dll"); - registerNativeLibrary("glfw-lwjgl3", Platform.Linux32, "native/linux/libglfw32.so"); - registerNativeLibrary("glfw-lwjgl3", Platform.Linux64, "native/linux/libglfw.so"); - registerNativeLibrary("glfw-lwjgl3", Platform.MacOSX32, "native/macosx/libglfw.dylib"); - registerNativeLibrary("glfw-lwjgl3", Platform.MacOSX64, "native/macosx/libglfw.dylib"); - - // jemalloc for LWJGL 3.x - registerNativeLibrary("jemalloc-lwjgl3", Platform.Windows32, "native/windows/jemalloc32.dll"); - registerNativeLibrary("jemalloc-lwjgl3", Platform.Windows64, "native/windows/jemalloc.dll"); - registerNativeLibrary("jemalloc-lwjgl3", Platform.Linux32, "native/linux/libjemalloc32.so"); - registerNativeLibrary("jemalloc-lwjgl3", Platform.Linux64, "native/linux/libjemalloc.so"); - registerNativeLibrary("jemalloc-lwjgl3", Platform.MacOSX32, "native/macosx/libjemalloc.dylib"); - registerNativeLibrary("jemalloc-lwjgl3", Platform.MacOSX64, "native/macosx/libjemalloc.dylib"); + // LWJGL 2 + registerNativeLibrary("lwjgl", Platform.Windows32, "lwjgl.dll"); + registerNativeLibrary("lwjgl", Platform.Windows64, "lwjgl64.dll"); + registerNativeLibrary("lwjgl", Platform.Linux32, "liblwjgl.so"); + registerNativeLibrary("lwjgl", Platform.Linux64, "liblwjgl64.so"); + registerNativeLibrary("lwjgl", Platform.MacOSX32, "liblwjgl.dylib"); + registerNativeLibrary("lwjgl", Platform.MacOSX64, "liblwjgl.dylib"); - // OpenAL for LWJGL 3.x + // OpenAL for LWJGL 2 // For OSX: Need to add lib prefix when extracting - registerNativeLibrary("openal-lwjgl3", Platform.Windows32, "native/windows/OpenAL32.dll"); - registerNativeLibrary("openal-lwjgl3", Platform.Windows64, "native/windows/OpenAL.dll"); - registerNativeLibrary("openal-lwjgl3", Platform.Linux32, "native/linux/libopenal32.so"); - registerNativeLibrary("openal-lwjgl3", Platform.Linux64, "native/linux/libopenal.so"); - registerNativeLibrary("openal-lwjgl3", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib"); - registerNativeLibrary("openal-lwjgl3", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib"); + registerNativeLibrary("openal", Platform.Windows32, "OpenAL32.dll"); + registerNativeLibrary("openal", Platform.Windows64, "OpenAL64.dll"); + registerNativeLibrary("openal", Platform.Linux32, "libopenal.so"); + registerNativeLibrary("openal", Platform.Linux64, "libopenal64.so"); + registerNativeLibrary("openal", Platform.MacOSX32, "openal.dylib", "libopenal.dylib"); + registerNativeLibrary("openal", Platform.MacOSX64, "openal.dylib", "libopenal.dylib"); // BulletJme registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll"); @@ -184,16 +154,16 @@ public static void registerNativeLibrary(String name, Platform platform, // JInput // For OSX: Need to rename extension jnilib -> dylib when extracting - registerNativeLibrary("jinput", Platform.Windows32, "native/windows/jinput-raw.dll"); - registerNativeLibrary("jinput", Platform.Windows64, "native/windows/jinput-raw_64.dll"); - registerNativeLibrary("jinput", Platform.Linux32, "native/windows/libjinput-linux.so"); - registerNativeLibrary("jinput", Platform.Linux64, "native/windows/libjinput-linux64.so"); - registerNativeLibrary("jinput", Platform.MacOSX32, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib"); - registerNativeLibrary("jinput", Platform.MacOSX64, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib"); + registerNativeLibrary("jinput", Platform.Windows32, "jinput-raw.dll"); + registerNativeLibrary("jinput", Platform.Windows64, "jinput-raw_64.dll"); + registerNativeLibrary("jinput", Platform.Linux32, "libjinput-linux.so"); + registerNativeLibrary("jinput", Platform.Linux64, "libjinput-linux64.so"); + registerNativeLibrary("jinput", Platform.MacOSX32, "libjinput-osx.jnilib", "libjinput-osx.dylib"); + registerNativeLibrary("jinput", Platform.MacOSX64, "libjinput-osx.jnilib", "libjinput-osx.dylib"); // JInput Auxiliary (only required on Windows) - registerNativeLibrary("jinput-dx8", Platform.Windows32, "native/windows/jinput-dx8.dll"); - registerNativeLibrary("jinput-dx8", Platform.Windows64, "native/windows/jinput-dx8_64.dll"); + registerNativeLibrary("jinput-dx8", Platform.Windows32, "jinput-dx8.dll"); + registerNativeLibrary("jinput-dx8", Platform.Windows64, "jinput-dx8_64.dll"); registerNativeLibrary("jinput-dx8", Platform.Linux32, null); registerNativeLibrary("jinput-dx8", Platform.Linux64, null); registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null); @@ -294,19 +264,14 @@ private static File getJmeUserCacheFolder() { File userHomeFolder = new File(System.getProperty("user.home")); File userCacheFolder = null; - switch (JmeSystem.getPlatform()) { - case Linux32: - case Linux64: + switch (JmeSystem.getPlatform().getOs()) { + case Linux: userCacheFolder = new File(userHomeFolder, ".cache"); break; - case MacOSX32: - case MacOSX64: - case MacOSX_PPC32: - case MacOSX_PPC64: + case MacOS: userCacheFolder = new File(new File(userHomeFolder, "Library"), "Caches"); break; - case Windows32: - case Windows64: + case Windows: userCacheFolder = new File(new File(userHomeFolder, "AppData"), "Local"); break; } diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java index aa02e3ab19c..01dbf7df2a0 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java +++ b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java @@ -119,17 +119,9 @@ protected void loadNatives() { return; } - if ("LWJGL".equals(settings.getAudioRenderer())) { - NativeLibraryLoader.loadNativeLibrary("openal-lwjgl3", true); - } - if (NativeLibraryLoader.isUsingNativeBullet()) { NativeLibraryLoader.loadNativeLibrary("bulletjme", true); } - - NativeLibraryLoader.loadNativeLibrary("glfw-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("jemalloc-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("lwjgl3", true); } /** From 7854851a723a818a27cd429f9b35c580f0b4c954 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 25 Feb 2023 21:52:42 -0800 Subject: [PATCH 114/140] solve issue #1969: missing check in GLRenderer.clearVertexAttribs() (#1970) --- .../java/com/jme3/renderer/opengl/GLRenderer.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index d14705d14d5..82bcca3a4f6 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -2922,13 +2922,16 @@ public void clearVertexAttribs() { for (int i = 0; i < attribList.oldLen; i++) { int idx = attribList.oldList[i]; gl.glDisableVertexAttribArray(idx); - VertexBuffer buffer = context.boundAttribs[idx].get(); - if (buffer != null && buffer.isInstanced()) { - glext.glVertexAttribDivisorARB(idx, 0); + WeakReference ref = context.boundAttribs[idx]; + if (ref != null) { + VertexBuffer buffer = ref.get(); + if (buffer != null && buffer.isInstanced()) { + glext.glVertexAttribDivisorARB(idx, 0); + } + context.boundAttribs[idx] = null; } - context.boundAttribs[idx] = null; } - context.attribIndexList.copyNewToOld(); + attribList.copyNewToOld(); } public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { From 72ffe5ed7630d1b0b5bb42cbb14433f959382570 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Thu, 2 Mar 2023 00:57:23 -0800 Subject: [PATCH 115/140] solve issue #1975: TestAttachDriver doesn't reset properly (#1976) --- .../src/main/java/jme3test/bullet/TestAttachDriver.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java index 32bb9c24021..8c1c7126037 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -291,7 +291,9 @@ public void onAction(String binding, boolean value, float tpf) { vehicle.setAngularVelocity(Vector3f.ZERO); vehicle.resetSuspension(); bridge.setPhysicsLocation(new Vector3f(0,1.4f,4)); - bridge.setPhysicsRotation(Quaternion.DIRECTION_Z.toRotationMatrix()); + bridge.setPhysicsRotation(Matrix3f.IDENTITY); + bridge.setLinearVelocity(Vector3f.ZERO); + bridge.setAngularVelocity(Vector3f.ZERO); } } } From 44a50de4d7797dc3dd8d46cd1f51308e9a05d627 Mon Sep 17 00:00:00 2001 From: Ali-RS Date: Thu, 2 Mar 2023 12:30:00 +0330 Subject: [PATCH 116/140] Improve NativeLibraryLoader (#1973) * Improve NativeLibraryLoader. * Add javadoc. * Moved library extraction requirement check into a separate method. * Fix javadoc. * Refactor library extraction check method. * Extract natives to system temp directory retrieved by System.getProperty("java.io.tmpdir") instead of working directory. * Renamed enum "Openal" to "OpenAL" and added javadoc on NativeLibraries. * Update comments. --- .../com/jme3/system/JmeDesktopSystem.java | 2 +- .../java/com/jme3/system/NativeLibraries.java | 235 ++++++++++++++ .../java/com/jme3/system/NativeLibrary.java | 81 ++++- .../com/jme3/system/NativeLibraryLoader.java | 298 ++++++------------ .../java/jme3test/audio/TestMusicPlayer.java | 5 +- .../com/jme3/system/lwjgl/LwjglContext.java | 10 +- .../com/jme3/system/lwjgl/LwjglContextVR.java | 2 +- 7 files changed, 412 insertions(+), 221 deletions(-) create mode 100644 jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 11c06c4622e..645a83b9ed7 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -285,7 +285,7 @@ public void initialize(AppSettings settings) { logger.log(Level.INFO, getBuildInfo()); if (!lowPermissions) { if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.BulletJme.getName(), true); } } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java new file mode 100644 index 00000000000..b64a2fa84bf --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2009-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.system; + +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Defines default native libraries that are loaded by + * {@link NativeLibraryLoader}. + * + * @author Ali-RS + */ +public enum NativeLibraries { + + // Note: LWJGL 3 handles its native library extracting & loading using + // its own SharedLibraryLoader. + + /** + * Native lwjgl libraries for LWJGL 2 required by jme3-lwjgl backend. + */ + Lwjgl(new LibraryInfo("lwjgl", libPath -> + // Delegate loading to lwjgl. + System.setProperty("org.lwjgl.librarypath", + Paths.get(libPath).getParent().toAbsolutePath().toString())) + .addNativeVariant(Platform.Windows32, "lwjgl.dll") + .addNativeVariant(Platform.Windows64, "lwjgl64.dll") + .addNativeVariant(Platform.Linux32, "liblwjgl.so") + .addNativeVariant(Platform.Linux64, "liblwjgl64.so") + .addNativeVariant(Platform.MacOSX32, "liblwjgl.dylib") + .addNativeVariant(Platform.MacOSX64, "liblwjgl.dylib") + ), + + // OpenAL for LWJGL 2 + // For OSX: Need to add lib prefix when extracting + /** + * Native OpenAL audio libraries for LWJGL 2 required by jme3-lwjgl backend. + */ + OpenAL(new LibraryInfo("openal") + .addNativeVariant(Platform.Windows32, "OpenAL32.dll") + .addNativeVariant(Platform.Windows64, "OpenAL64.dll") + .addNativeVariant(Platform.Linux32, "libopenal.so") + .addNativeVariant(Platform.Linux64, "libopenal64.so") + .addNativeVariant(Platform.MacOSX32, "openal.dylib", "libopenal.dylib") + .addNativeVariant(Platform.MacOSX64, "openal.dylib", "libopenal.dylib") + ), + + /** + * Native bullet physics libraries required by Minie library. + */ + BulletJme(new LibraryInfo("bulletjme") + .addNativeVariant(Platform.Windows32, "native/windows/x86/bulletjme.dll", "bulletjme-x86.dll") + .addNativeVariant(Platform.Windows64, "native/windows/x86_64/bulletjme.dll", "bulletjme-x86_64.dll") + .addNativeVariant(Platform.Windows_ARM64, "native/windows/arm64/bulletjme.dll", "bulletjme-arm64.dll") + .addNativeVariant(Platform.Linux32, "native/linux/x86/libbulletjme.so", "libbulletjme-x86.so") + .addNativeVariant(Platform.Linux64, "native/linux/x86_64/libbulletjme.so", "libbulletjme-x86_64.so") + .addNativeVariant(Platform.Linux_ARM32, "native/linux/arm32/libbulletjme.so", "libbulletjme-arm32.so") + .addNativeVariant(Platform.Linux_ARM64, "native/linux/arm64/libbulletjme.so", "libbulletjme-arm64.so") + .addNativeVariant(Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib", "libbulletjme-x86.dylib") + .addNativeVariant(Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib", "libbulletjme-x86_64.dylib") + .addNativeVariant(Platform.MacOSX_ARM64, "native/osx/arm64/libbulletjme.dylib", "libbulletjme-arm64.dylib") + ), + + // For OSX: Need to rename extension jnilib -> dylib when extracting + /** + * Native JInput joystick libraries required by jme3-lwjgl backend. + */ + JInput(new LibraryInfo("jinput", libPath -> + // Delegate loading to jinput. + System.setProperty("net.java.games.input.librarypath", + Paths.get(libPath).getParent().toAbsolutePath().toString())) + .addNativeVariant(Platform.Windows32, "jinput-raw.dll") + .addNativeVariant(Platform.Windows64, "jinput-raw_64.dll") + .addNativeVariant(Platform.Linux32, "libjinput-linux.so") + .addNativeVariant(Platform.Linux64, "libjinput-linux64.so") + .addNativeVariant(Platform.MacOSX32, "libjinput-osx.jnilib", "libjinput-osx.dylib") + .addNativeVariant(Platform.MacOSX64, "libjinput-osx.jnilib", "libjinput-osx.dylib") + ), + + /** + * Native JInput DirectX 8 auxiliary libraries required by jme3-lwjgl backend. + * (only required on Windows) + */ + JInputDX8(new LibraryInfo("jinput-dx8") + .addNativeVariant(Platform.Windows32, "jinput-dx8.dll", null) + .addNativeVariant(Platform.Windows64, "jinput-dx8_64.dll", null) + .addNativeVariant(Platform.Linux32, null) + .addNativeVariant(Platform.Linux64, null) + .addNativeVariant(Platform.MacOSX32, null) + .addNativeVariant(Platform.MacOSX64, null) + ); + + private final LibraryInfo library; + + + NativeLibraries(LibraryInfo library) { + this.library = library; + } + + /** + * Register native libraries on {@link NativeLibraryLoader} so we can load them + * later on via {@link NativeLibraryLoader#loadNativeLibrary(String, boolean)}. + */ + public static void registerDefaultLibraries() { + Lwjgl.registerLibrary(); + OpenAL.registerLibrary(); + BulletJme.registerLibrary(); + JInput.registerLibrary(); + JInputDX8.registerLibrary(); + } + + public LibraryInfo getLibrary() { + return library; + } + + /** + * @return the library name. This is effectively equivalent to the + * call {@link LibraryInfo#getName()} + */ + public String getName() { + return library.getName(); + } + + /** + * Registers this library's native variants into {@link NativeLibraryLoader} that can + * be loaded later via {@link NativeLibraryLoader#loadNativeLibrary(String, boolean)}. + */ + private void registerLibrary() { + library.getNativeVariants().forEach(NativeLibraryLoader::registerNativeLibrary); + } + + /** + * A helper class that defines a native library by name, list of its native variants + * for target platforms and a load function used to load library from an absolute + * path after extracted by {@link NativeLibraryLoader}. + */ + public static class LibraryInfo { + + private final String name; + private final List nativeVariants = new ArrayList<>(); + private final Consumer loadFunction; + + /** + * Define a library by the specified name and a default load function + * that uses {@link System#load(String)} to load extracted native from + * absolute path. + * @param name The library name. (not null) + */ + public LibraryInfo(String name) { + this(name, System::load); + } + + /** + * Define a library by the specified name and specified load function + * that is used to load extracted native from an absolute path string. + * + * @param name The library name (not null) + * @param loadFunction The load function for loading library from + * an absolute path string. (not null) + */ + public LibraryInfo(String name, Consumer loadFunction) { + this.name = name; + this.loadFunction = loadFunction; + } + + /** + * @return the library name. + */ + public String getName() { + return name; + } + + /** + * @return the list of native variants, each targeting a specific platform. + */ + public List getNativeVariants() { + return nativeVariants; + } + + /** + * Adds a new native library that targets specified platform. + * + * @param platform The platform this library targets + * @param pathInNativesJar The path of native file inside library jar + * @return this + */ + public LibraryInfo addNativeVariant(Platform platform, String pathInNativesJar) { + return addNativeVariant(platform, pathInNativesJar, null); + } + + /** + * Adds a new native library that targets specified platform. + * + * @param platform The platform this library targets + * @param pathInNativesJar The path of native file inside library jar + * @param extractedAsFileName The filename that the library should be extracted as + * @return this + */ + public LibraryInfo addNativeVariant(Platform platform, String pathInNativesJar, String extractedAsFileName) { + nativeVariants.add(new NativeLibrary(name, platform, pathInNativesJar, extractedAsFileName, loadFunction)); + return this; + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java index 1ce38b0477d..a4ad86c55a8 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,8 @@ */ package com.jme3.system; +import java.util.function.Consumer; + /** * Holds information about a native library for a particular platform. * @@ -42,6 +44,7 @@ final class NativeLibrary { private final Platform platform; private final String pathInNativesJar; private final String extractedAsFileName; + private final Consumer loadFunction; /** * Key for map to find a library for a name and platform. @@ -76,6 +79,55 @@ public boolean equals(Object obj) { return true; } } + + /** + * Create a new NativeLibrary. The extracted file name will be the same as + * in jar and will be loaded via {@link System#load(String)}. + * + * @param name The name of the library (not null) + * @param platform The platform associated to this native library (not null) + * @param pathInNativesJar The path to the library in the classpath (if it is null, + * the library loading will be ignored on this platform) + **/ + public NativeLibrary(String name, Platform platform, String pathInNativesJar) { + this(name, platform, pathInNativesJar, null); + } + + /** + * Create a new NativeLibrary. The extracted file will be loaded + * via {@link System#load(String)}. + * + * @param name The name of the library (not null) + * @param platform The platform associated to this native library (not null) + * @param pathInNativesJar The path to the library in the classpath (if it is null, + * the library loading will be ignored on this platform) + * @param extractedAsFileName The name that should be given to the extracted file + * (if set to null, then the filename in the natives + * jar shall be used) + */ + public NativeLibrary(String name, Platform platform, String pathInNativesJar, String extractedAsFileName) { + this(name, platform, pathInNativesJar, extractedAsFileName, System::load); + } + + /** + * Create a new NativeLibrary. + * + * @param name The name of the library (not null) + * @param platform The platform associated to this native library (not null) + * @param pathInNativesJar The path to the library in the classpath (if it is null, + * the library loading will be ignored on this platform) + * @param extractedAsFileName The name that should be given to the extracted file + * (if set to null, then the filename in the natives + * jar shall be used) + * @param loadFunction The function used to load the library from absolute path (not null) + */ + public NativeLibrary(String name, Platform platform, String pathInNativesJar, String extractedAsFileName, Consumer loadFunction) { + this.name = name; + this.platform = platform; + this.pathInNativesJar = pathInNativesJar; + this.extractedAsFileName = extractedAsFileName; + this.loadFunction = loadFunction; + } /** * The name of the library. @@ -90,7 +142,7 @@ public String getName() { /** * The OS + architecture combination for which this library * should be extracted. - * + * * @return platform associated to this native library */ public Platform getPlatform() { @@ -99,12 +151,12 @@ public Platform getPlatform() { /** * The filename that the library should be extracted as. - * + * * In some cases, this differs from the {@link #getPathInNativesJar() path in the natives jar}, * since the names of the libraries specified in the jars are often incorrect. * If set to null, then the filename in the * natives jar shall be used. - * + * * @return the name that should be given to the extracted file. */ public String getExtractedAsName() { @@ -113,10 +165,10 @@ public String getExtractedAsName() { /** * Path inside the natives jar or classpath where the library is located. - * + * * This library must be compatible with the {@link #getPlatform() platform} * which this library is associated with. - * + * * @return path to the library in the classpath */ public String getPathInNativesJar() { @@ -124,19 +176,18 @@ public String getPathInNativesJar() { } /** - * Create a new NativeLibrary. + * @return the load function used for loading this native library. + * It loads the native library from absolute path on disk. + * By default, it loads with {@link System#load(java.lang.String) }. */ - public NativeLibrary(String name, Platform platform, String pathInNativesJar, String extractedAsFileName) { - this.name = name; - this.platform = platform; - this.pathInNativesJar = pathInNativesJar; - this.extractedAsFileName = extractedAsFileName; + public Consumer getLoadFunction() { + return loadFunction; } /** - * Create a new NativeLibrary. + * @return key for map to find a library for a name and platform. */ - public NativeLibrary(String name, Platform platform, String pathInNativesJar) { - this(name, platform, pathInNativesJar, null); + public Key getKey() { + return new NativeLibrary.Key(getName(), getPlatform()); } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index 8c5c790be28..209034c1e85 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -35,6 +35,9 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -70,15 +73,27 @@ public final class NativeLibraryLoader { private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName()); - private static final byte[] buf = new byte[1024 * 100]; private static File extractionFolderOverride = null; private static File extractionFolder = null; - private static final HashMap nativeLibraryMap - = new HashMap<>(); - + private static final HashMap nativeLibraryMap = new HashMap<>(); + + static { + NativeLibraries.registerDefaultLibraries(); + } + + /** + * Register a new native library. + * + * This simply registers a known library, the actual extraction and loading + * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }. + */ + public static void registerNativeLibrary(NativeLibrary library) { + nativeLibraryMap.put(library.getKey(), library); + } + /** - * Register a new known library. + * Register a new native library. * * This simply registers a known library, the actual extraction and loading * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }. @@ -99,7 +114,7 @@ public static void registerNativeLibrary(String name, Platform platform, } /** - * Register a new known JNI library. + * Register a new native library. * * This simply registers a known library, the actual extraction and loading * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }. @@ -119,57 +134,6 @@ public static void registerNativeLibrary(String name, Platform platform, registerNativeLibrary(name, platform, path, null); } - static { - // Note: LWJGL 3 handles its native library extracting & loading using - // its own SharedLibraryLoader. - - // LWJGL 2 - registerNativeLibrary("lwjgl", Platform.Windows32, "lwjgl.dll"); - registerNativeLibrary("lwjgl", Platform.Windows64, "lwjgl64.dll"); - registerNativeLibrary("lwjgl", Platform.Linux32, "liblwjgl.so"); - registerNativeLibrary("lwjgl", Platform.Linux64, "liblwjgl64.so"); - registerNativeLibrary("lwjgl", Platform.MacOSX32, "liblwjgl.dylib"); - registerNativeLibrary("lwjgl", Platform.MacOSX64, "liblwjgl.dylib"); - - // OpenAL for LWJGL 2 - // For OSX: Need to add lib prefix when extracting - registerNativeLibrary("openal", Platform.Windows32, "OpenAL32.dll"); - registerNativeLibrary("openal", Platform.Windows64, "OpenAL64.dll"); - registerNativeLibrary("openal", Platform.Linux32, "libopenal.so"); - registerNativeLibrary("openal", Platform.Linux64, "libopenal64.so"); - registerNativeLibrary("openal", Platform.MacOSX32, "openal.dylib", "libopenal.dylib"); - registerNativeLibrary("openal", Platform.MacOSX64, "openal.dylib", "libopenal.dylib"); - - // BulletJme - registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll"); - registerNativeLibrary("bulletjme", Platform.Windows64, "native/windows/x86_64/bulletjme.dll"); - registerNativeLibrary("bulletjme", Platform.Windows_ARM64, "native/windows/arm64/bulletjme.dll"); - registerNativeLibrary("bulletjme", Platform.Linux32, "native/linux/x86/libbulletjme.so"); - registerNativeLibrary("bulletjme", Platform.Linux64, "native/linux/x86_64/libbulletjme.so"); - registerNativeLibrary("bulletjme", Platform.Linux_ARM32, "native/linux/arm32/libbulletjme.so"); - registerNativeLibrary("bulletjme", Platform.Linux_ARM64, "native/linux/arm64/libbulletjme.so"); - registerNativeLibrary("bulletjme", Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib"); - registerNativeLibrary("bulletjme", Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib"); - registerNativeLibrary("bulletjme", Platform.MacOSX_ARM64, "native/osx/arm64/libbulletjme.dylib"); - - // JInput - // For OSX: Need to rename extension jnilib -> dylib when extracting - registerNativeLibrary("jinput", Platform.Windows32, "jinput-raw.dll"); - registerNativeLibrary("jinput", Platform.Windows64, "jinput-raw_64.dll"); - registerNativeLibrary("jinput", Platform.Linux32, "libjinput-linux.so"); - registerNativeLibrary("jinput", Platform.Linux64, "libjinput-linux64.so"); - registerNativeLibrary("jinput", Platform.MacOSX32, "libjinput-osx.jnilib", "libjinput-osx.dylib"); - registerNativeLibrary("jinput", Platform.MacOSX64, "libjinput-osx.jnilib", "libjinput-osx.dylib"); - - // JInput Auxiliary (only required on Windows) - registerNativeLibrary("jinput-dx8", Platform.Windows32, "jinput-dx8.dll"); - registerNativeLibrary("jinput-dx8", Platform.Windows64, "jinput-dx8_64.dll"); - registerNativeLibrary("jinput-dx8", Platform.Linux32, null); - registerNativeLibrary("jinput-dx8", Platform.Linux64, null); - registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null); - registerNativeLibrary("jinput-dx8", Platform.MacOSX64, null); - } - private NativeLibraryLoader() { } @@ -211,8 +175,8 @@ public static void setCustomExtractionFolder(String path) { *