diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 191481e66f..23d0923658 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 @@ -58,17 +59,17 @@ 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 steps: - name: Clone the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 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 \ @@ -80,7 +81,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 jdk17 BuildJMonkey: needs: [BuildAndroidNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} @@ -88,32 +89,34 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-18.04,ubuntu-20.04,windows-2019,macOS-latest] - jdk: [8.x.x,11.x.x] + os: [ubuntu-latest,windows-2019,macOS-latest] + jdk: [8, 11, 17] include: - - os: ubuntu-20.04 - osName: linux-next - - os: ubuntu-18.04 + - os: ubuntu-latest osName: linux deploy: true - os: windows-2019 osName: windows + deploy: false - os: macOS-latest osName: mac - - jdk: 11.x.x + deploy: false + - jdk: 8 + deploy: false + - jdk: 11 deploy: false steps: - name: Clone the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 - name: Setup the java environment - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'temurin' java-version: ${{ matrix.jdk }} - architecture: x64 - name: Download natives for android uses: actions/download-artifact@master @@ -122,7 +125,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: | @@ -203,10 +206,10 @@ jobs: # The snapshot is downloaded when people build the engine without setting buildNativeProject # this is useful for people that want to build only the java part and don't have # all the stuff needed to compile natives. - DeploySnapshot: + DeployNativeSnapshot: needs: [BuildJMonkey] - name: "Deploy snapshot" - runs-on: ubuntu-18.04 + name: "Deploy native snapshot" + runs-on: ubuntu-latest if: github.event_name == 'push' steps: @@ -288,20 +291,71 @@ jobs: fi fi + # This job deploys snapshots on the master branch + DeployJavaSnapshot: + needs: [BuildJMonkey] + name: Deploy Java Snapshot + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref_name == 'master' + steps: + + # We need to clone everything again for uploadToMaven.sh ... + - name: Clone the repo + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + # Setup jdk 17 used for building Maven-style artifacts + - name: Setup the java environment + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Download natives for android + uses: actions/download-artifact@master + with: + name: android-natives + path: build/native + + - name: Rebuild the maven artifacts and deploy them to the Sonatype repository + run: | + if [ "${{ secrets.OSSRH_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable deployment to Sonatype:" + echo "OSSRH_PASSWORD, OSSRH_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + else + ./gradlew publishMavenPublicationToSNAPSHOTRepository \ + -PossrhPassword=${{ secrets.OSSRH_PASSWORD }} \ + -PossrhUsername=${{ secrets.OSSRH_USERNAME }} \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + fi + + # This job deploys the release DeployRelease: needs: [BuildJMonkey] name: Deploy Release - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest if: github.event_name == 'release' steps: # 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 + # 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 @@ -366,7 +420,7 @@ jobs: DeployJavaDoc: needs: [BuildJMonkey] name: Deploy Javadoc - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest if: github.event_name == 'release' steps: diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a9e542c6ba..0000000000 --- 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 0000000000..0d6db16108 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,30 @@ +Copyright (c) 2009-2023 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/README.md b/README.md index 6eb72e4179..f267b36b85 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,13 @@ 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.3.2 is the latest stable version of the jMonkeyEngine SDK. +v3.6.0 is the latest stable version of the engine. The engine is used by several commercial game studios and computer-science courses. Here's a taste: ![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/) @@ -27,6 +25,12 @@ 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) + - [Exotic Matter](https://exoticmatter.io) + - [Demon Lord (on Google Play)](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1) + - [Wild Magic](http://wildmagicgame.ru/) + - [Marvelous Marbles (on Steam)](https://store.steampowered.com/app/2244540/Marvelous_Marbles/) + - [Boxer (on Google Play)](https://play.google.com/store/apps/details?id=com.tharg.boxer) ## Getting started @@ -53,5 +57,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) diff --git a/build.gradle b/build.gradle index f2bee119ad..2a7b4ed0c5 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' @@ -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') @@ -274,7 +283,7 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { //} wrapper { - gradleVersion = '6.9.2' + gradleVersion = '7.6' } diff --git a/common.gradle b/common.gradle index da1f3461b1..e97fe498ad 100644 --- a/common.gradle +++ b/common.gradle @@ -20,10 +20,14 @@ 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 { - lwjgl3Version = '3.3.1' // used in both the jme3-lwjgl3 and jme3-vr build scripts + lwjgl3Version = '3.3.2' // 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 { @@ -37,10 +41,9 @@ 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.17' } - // Uncomment if you want to see the status of every test that is run and // the test output. /* @@ -55,7 +58,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")})" } } @@ -167,6 +171,14 @@ publishing { name = 'OSSRH' url = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2' } + maven { + credentials { + username = gradle.rootProject.hasProperty('ossrhUsername') ? ossrhUsername : 'Unknown user' + password = gradle.rootProject.hasProperty('ossrhPassword') ? ossrhPassword : 'Unknown password' + } + name = 'SNAPSHOT' + url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + } } } diff --git a/gradle.properties b/gradle.properties index 0bc6014997..3f377cdfb6 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 ) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023..943f0cbfa7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ec991f9aa1..508322917b 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-6.9.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337..65dcd68d65 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 @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/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 ac1b06f938..6689b85bee 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,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,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 +76,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 diff --git a/jme3-android-native/bufferallocator.gradle b/jme3-android-native/bufferallocator.gradle new file mode 100644 index 0000000000..f03027e275 --- /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 d0618bb1ea..cc78ab4722 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 0000000000..d735478fb6 --- /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 0000000000..4bcc2ef85e --- /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 0000000000..4f5cd66d09 --- /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-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 ceebbd81ba..25a15f5b8a 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 bd6ecc6273..4020295660 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,46 @@ +/* + * 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}. + * + * @author Kirill Vainer + * @author Modified by pavl_g + */ public class NativeVorbisFile { public int fd; @@ -16,22 +54,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 3093b0d4b2..63517cd3d8 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(); 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 e36319c0bc..0d6ee82c6d 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-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index 6d34238100..5757368ef8 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 @@ -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; @@ -54,8 +57,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 +85,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()); } } @@ -257,6 +260,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; @@ -484,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-android/src/main/java/com/jme3/util/AndroidBufferAllocator.java b/jme3-android/src/main/java/com/jme3/util/AndroidBufferAllocator.java index 596bafc138..6ddc00f17e 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 0000000000..f91334db7e --- /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 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 0000000000..5b820343d2 --- /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 c897ab3543..d364ab0923 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 7921e232bf..a0174b074a 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 de93451227..14a7d9ca58 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 5e1013ae66..284b8e97e5 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 89f6bf88c4..d92fd19479 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 0000000000..38d15448bf --- /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 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 6ebf2f945f..7234abfaa7 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 diff --git a/jme3-awt-dialogs/build.gradle b/jme3-awt-dialogs/build.gradle new file mode 100644 index 0000000000..9dd715218e --- /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 0000000000..002f80a20b --- /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 77% 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 003ea21bca..34744b2298 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.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,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; @@ -42,8 +45,12 @@ 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.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; @@ -62,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 @@ -107,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. * @@ -121,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) @@ -138,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"); } @@ -146,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); } @@ -181,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) { @@ -211,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(); } @@ -250,9 +314,6 @@ public void setMinHeight(int minHeight) { this.minHeight = minHeight; } - - - /** * setImage sets the background image of the dialog. * @@ -264,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); } } @@ -281,28 +342,22 @@ 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()); - - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - logger.warning("Could not set native look and feel."); - } addWindowListener(new WindowAdapter() { @@ -314,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); @@ -334,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(); } @@ -361,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; @@ -374,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; @@ -436,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() { @@ -446,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(); } @@ -470,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; @@ -488,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); @@ -595,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); @@ -603,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(); @@ -611,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; @@ -627,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() { @@ -672,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. @@ -688,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(); @@ -712,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)]); } // @@ -736,23 +793,23 @@ 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; - - 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; @@ -760,34 +817,31 @@ private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthL if (width >= widthLimit) { width = widthLimit; } - + String res = width + " x " + height; - if (!resolutions.contains(res)) { - resolutions.add(res); - } + resolutions.add(res); } } - String[] res = new String[resolutions.size()]; - resolutions.toArray(res); - return res; + 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 (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(); } } @@ -798,79 +852,78 @@ 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")) { - // add some default depths, possible system is multi-depth supporting - depths.clear(); + if (depths.isEmpty()) { + // add some default depths, possible system is multi-depth + // supporting depths.add("24 bpp"); } - String[] res = new String[depths.size()]; - depths.toArray(res); - return res; + return depths.toArray(new String[0]); } /** * Returns every possible refresh rate for the given resolution. */ - 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(); + private static String[] getFrequencies(String resolution, DisplayMode[] modes) { + 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]); } - + /** * 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; 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(); } } } - + if (closest != Integer.MAX_VALUE) { return closest + " Hz"; } else { @@ -879,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 0000000000..7c5b12c97d --- /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/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index 4bc6c383a1..d06b0816cc 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 9b9990e91d..0c3c97e998 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/MorphTrack.java b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java index e4c6fdd233..ee767d2885 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 b226c6d7a5..3492d17eec 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 89cde24dda..13c7d2f183 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); 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 d4200d71b0..b6ca9c8873 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,62 @@ 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); + } + + /** + * 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); } @@ -644,4 +700,112 @@ 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 + "]"; + } + } + + 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-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 31f191678b..99c559fca6 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/BlendAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java index 51b7ae3f85..401a9d2cce 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-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 4ebf3ede2b..c9882529f9 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,26 @@ protected float getTransitionWeight() { return transitionWeight; } + /** + * @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) { + if (maxTransitionWeight < 0.0 || maxTransitionWeight > 1.0) { + throw new IllegalArgumentException("maxTransitionWeight must be between 0 and 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 +173,7 @@ public TransitionTween(double length) { @Override protected void doInterpolate(double t) { - transitionWeight = (float) t; + transitionWeight = (float) Math.min(t, maxTransitionWeight); } } 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 f9ddc92657..198d730efa 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 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 6849a35edd..626dec4bfd 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/asset/CloneableSmartAsset.java b/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java index 74a00dae42..b428fd08d1 100644 --- a/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java +++ b/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.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 @@ -65,13 +65,14 @@ public interface CloneableSmartAsset extends Cloneable { public CloneableSmartAsset clone(); /** - * Set by the {@link AssetManager} to track this asset. + * Assigns the specified AssetKey to the asset. * - * Only clones of the asset has this set, the original copy that - * was loaded has this key set to null so that only the clones are tracked - * for garbage collection. + * This is invoked by the {@link AssetManager}. + * Only clones of the asset have non-null keys. The original copy that + * was loaded has no key assigned. Only the clones are tracked + * for garbage collection. * - * @param key The AssetKey to set + * @param key The AssetKey to assign */ public void setKey(AssetKey key); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java b/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java index 80f5f63040..249d9601eb 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.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 @@ -132,6 +132,6 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_AUDIOBUFFER << 32) | ((long) id); + return ((long) OBJTYPE_AUDIOBUFFER << 32) | (0xffffffffL & (long) id); } } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java index ccc74e181c..15b40c5b43 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioStream.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 @@ -222,6 +222,6 @@ public void setTime(float time) { @Override public long getUniqueId() { - return ((long) OBJTYPE_AUDIOSTREAM << 32) | ((long) ids[0]); + return ((long) OBJTYPE_AUDIOSTREAM << 32) | (0xffffffffL & (long) ids[0]); } } diff --git a/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java b/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java index aaf1f5b69b..daa49adb65 100644 --- a/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java +++ b/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.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 @@ -99,6 +99,6 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_FILTER << 32) | ((long) id); + return ((long) OBJTYPE_FILTER << 32) | (0xffffffffL & (long) id); } } 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 0cbfca11e0..a2d481f36b 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 @@ -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 @@ -1081,6 +1085,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 +1101,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) { @@ -1169,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); } } 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 10f7ace1b1..8f43bec5b2 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 f34dd7dc0d..62a214e2fd 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); } 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 8734ae92aa..d6f688662f 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 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 0000000000..55f46d1802 --- /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/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java index 3bd50332fc..35111331be 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/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java index fcbedab1ed..e0be1f0563 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-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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, /** @@ -480,6 +479,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 +511,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 +558,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); @@ -588,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) { @@ -695,6 +709,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){ @@ -877,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) { @@ -942,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 @@ -956,6 +983,9 @@ 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. */ public void setLineWidth(float lineWidth) { @@ -1129,7 +1159,81 @@ public TestFunction getBackStencilFunction() { } /** - * Retrieve the blend equation. + * 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 the desired bitmask (default=0x7fffffff) + */ + public void setBackStencilMask(int backStencilMask) { + this.backStencilMask = backStencilMask; + } + + /** + * 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 the desired bitmask (default=0x0) + */ + public void setBackStencilReference(int backStencilReference) { + this.backStencilReference = backStencilReference; + } + + /** + * 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 the reference value for the front stencil + */ + public int getFrontStencilReference() { + return frontStencilReference; + } + + /** + * 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 the reference value for the back stencil + */ + public int getBackStencilReference() { + return backStencilReference; + } + + /** + * Returns the blend equation. * * @return the blend equation. */ @@ -1138,7 +1242,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. */ @@ -1147,7 +1251,7 @@ public BlendEquationAlpha getBlendEquationAlpha() { } /** - * Retrieve the blend mode. + * Returns the blend mode. * * @return the blend mode. */ @@ -1156,7 +1260,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. @@ -1166,7 +1270,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. @@ -1176,7 +1280,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. @@ -1186,7 +1290,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 bed5008c5b..5b4d6d2634 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 99280a826d..6c6e7449e2 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-2023 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,76 @@ public void multLocal(Quaternion rotation) { } /** - * Create a copy. + * 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. * * @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 1a1ab0732d..0974a38b7e 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) */ @@ -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() { @@ -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 53e203305b..c172d26d4a 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 fed4a30b67..1c21d3b087 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 a662978521..89efe8a6fd 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/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java index 5422a3411b..f7b6d185df 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 { 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 89ecf4e17f..961d78ae6f 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 fa97f38626..a1b453a096 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 f6deaa165e..753bf2f1a6 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 */ @@ -519,4 +518,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 0ecc8432e5..cf8aeb790f 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 39184475e1..ea9add1583 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 91ba96d9bb..82bcca3a4f 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 @@ -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" + @@ -949,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); } @@ -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()); + } } @@ -2883,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) { @@ -2995,6 +3037,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/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 05f7a7a8a3..fb5a6f7dfb 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); } 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 eacaadb7af..2b9498a517 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 @@ -368,7 +368,7 @@ public boolean checkCulling(Camera cam) { } CullHint cm = getCullHint(); - assert cm != CullHint.Inherit; + assert cm != CullHint.Inherit : "CullHint should never be inherit. Problem spatial name: " + getName(); if (cm == Spatial.CullHint.Always) { setLastFrustumIntersection(Camera.FrustumIntersect.Outside); return false; @@ -586,7 +586,7 @@ protected void updateWorldLightList() { worldLights.update(localLights, null); refreshFlags &= ~RF_LIGHTLIST; } else { - assert (parent.refreshFlags & RF_LIGHTLIST) == 0; + assert (parent.refreshFlags & RF_LIGHTLIST) == 0 : "Illegal light list update. Problem spatial name: " + getName(); worldLights.update(localLights, parent.worldLights); refreshFlags &= ~RF_LIGHTLIST; } @@ -599,7 +599,7 @@ protected void updateMatParamOverrides() { if (parent == null) { worldOverrides.addAll(localOverrides); } else { - assert (parent.refreshFlags & RF_MATPARAM_OVERRIDE) == 0; + assert (parent.refreshFlags & RF_MATPARAM_OVERRIDE) == 0 : "Illegal mat param update. Problem spatial name: " + getName(); worldOverrides.addAll(parent.worldOverrides); worldOverrides.addAll(localOverrides); } @@ -653,7 +653,7 @@ protected void updateWorldTransforms() { refreshFlags &= ~RF_TRANSFORM; } else { // check if transform for parent is updated - assert ((parent.refreshFlags & RF_TRANSFORM) == 0); + assert ((parent.refreshFlags & RF_TRANSFORM) == 0) : "Illegal rf transform update. Problem spatial name: " + getName(); worldTransform.set(localTransform); worldTransform.combineWithParent(parent.worldTransform); refreshFlags &= ~RF_TRANSFORM; @@ -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 : "Surprising control remove failure. " + control.getClass().getSimpleName() + " from spatial " + getName(); + controls.add(index, control); + } + } + /** * Removes the first control that is an instance of the given class. * @@ -920,7 +952,7 @@ public void updateGeometricState() { if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { updateMatParamOverrides(); } - assert refreshFlags == 0; + assert refreshFlags == 0 : "Illegal refresh flags state: " + refreshFlags + " for spatial " + getName(); } /** 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 36b94137e8..8c0a388bb7 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.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 @@ -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. @@ -1115,7 +1116,7 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_VERTEXBUFFER << 32) | ((long) id); + return ((long) OBJTYPE_VERTEXBUFFER << 32) | (0xffffffffL & (long) id); } @Override @@ -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/scene/control/AreaUtils.java b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java index e5a7b4c98c..52f388c7df 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/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 42251611b6..70266e401d 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 @@ -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 @@ -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]; @@ -72,7 +75,6 @@ public class InstancedGeometry extends Geometry { private int firstUnusedIndex = 0; private int numVisibleInstances = 0; - private Camera cam; public InstancedGeometry() { super(); @@ -95,6 +97,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. * @@ -257,7 +275,7 @@ private void swap(int idx1, int idx2) { } } - public void updateInstances() { + public void updateInstances(Camera cam) { FloatBuffer fb = (FloatBuffer) transformInstanceData.getData(); fb.limit(fb.capacity()); fb.position(0); @@ -284,14 +302,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; } @@ -402,12 +415,6 @@ private void updateAllInstanceData() { allInstanceData = allData.toArray(new VertexBuffer[allData.size()]); } - @Override - public boolean checkCulling(Camera cam) { - this.cam = cam; - return super.checkCulling(cam); - } - @Override public int collideWith(Collidable other, CollisionResults results) { return 0; // Ignore collision @@ -458,4 +465,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; + } + } } diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java index ea5b576ed3..821264f9a6 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2021 jMonkeyEngine + * Copyright (c) 2014-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,7 @@ import com.jme3.export.JmeImporter; import com.jme3.material.MatParam; import com.jme3.material.Material; +import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; @@ -161,7 +162,7 @@ public void update(float tpf){ @Override public void render(RenderManager rm, ViewPort vp) { - node.renderFromControl(); + node.renderFromControl(vp.getCamera()); } @Override @@ -198,9 +199,9 @@ public InstancedNode(String name) { addControl(control); } - private void renderFromControl() { + private void renderFromControl(Camera cam) { for (InstancedGeometry ig : instancesMap.values()) { - ig.updateInstances(); + ig.updateInstances(cam); } } diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java index f1522ff5ba..b893576a87 100644 --- a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java +++ b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java @@ -829,6 +829,6 @@ protected void deleteNativeBuffers() { @Override public long getUniqueId() { - return ((long) OBJTYPE_BO << 32) | ((long) id); + return ((long) OBJTYPE_BO << 32) | (0xffffffffL & (long) id); } } diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index 7860e29b13..eb2f778d39 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.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 @@ -189,7 +189,7 @@ public String getDefines() { @Override public long getUniqueId() { - return ((long)OBJTYPE_SHADERSOURCE << 32) | ((long)id); + return ((long)OBJTYPE_SHADERSOURCE << 32) | (0xffffffffL & (long)id); } @Override @@ -462,6 +462,6 @@ public NativeObject createDestructableClone(){ @Override public long getUniqueId() { - return ((long)OBJTYPE_SHADER << 32) | ((long)id); + return ((long)OBJTYPE_SHADER << 32) | (0xffffffffL & (long)id); } } 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 877f47d0bb..3a1ca47fa2 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() { 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 384630b521..65cb39bc30 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 dcd49be09a..886abc45ec 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 7612342404..a0d405000b 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-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index cdc1324ea9..f3fb5a158f 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/system/JmeContext.java b/jme3-core/src/main/java/com/jme3/system/JmeContext.java index f54831919a..8d83e0960e 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. @@ -187,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/JmeDialogsFactory.java b/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java new file mode 100644 index 0000000000..cdd8fe8d47 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java @@ -0,0 +1,52 @@ +/* + * 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 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 message text to be displayed in the dialog + */ + 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 561926a9e7..4a832916c8 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 5e701f3a0f..35143fca4a 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/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index c18de49129..fd43d8d761 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; @@ -256,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-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java index 1b66f9bee7..7153f8200f 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.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 @@ -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; /** @@ -805,7 +806,7 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long) OBJTYPE_FRAMEBUFFER << 32) | ((long) id); + return ((long) OBJTYPE_FRAMEBUFFER << 32) | (0xffffffffL & (long) id); } /** @@ -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-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 69fcb86118..4f576b810c 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-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -698,7 +698,7 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long)OBJTYPE_TEXTURE << 32) | ((long)id); + return ((long)OBJTYPE_TEXTURE << 32) | (0xffffffffL & (long)id); } /** @@ -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); diff --git a/jme3-core/src/main/java/com/jme3/ui/Picture.java b/jme3-core/src/main/java/com/jme3/ui/Picture.java index 123c244939..705f72e0dc 100644 --- a/jme3-core/src/main/java/com/jme3/ui/Picture.java +++ b/jme3-core/src/main/java/com/jme3/ui/Picture.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 @@ -86,6 +86,24 @@ public Picture(String name){ protected Picture(){ } + /** + * Return the height of the picture. + * + * @return the height (in pixels) + */ + public float getHeight() { + return height; + } + + /** + * Return the width of the picture. + * + * @return the width (in pixels) + */ + public float getWidth() { + return width; + } + /** * Set the width in pixels of the picture, if the width * does not match the texture's width, then the texture will 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 0000000000..ce48dc763c --- /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; + } +} 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 cb5912907c..18417b6b4c 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/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java index 4f2dcfc87b..27375469e4 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/main/resources/Common/MatDefs/Light/PBRLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag index 6905b7f926..8357527009 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag @@ -84,11 +84,18 @@ varying vec3 wPosition; #ifdef LIGHTMAP uniform sampler2D m_LightMap; #endif + +#ifdef AO_STRENGTH + uniform float m_AoStrength; +#endif #if defined(NORMALMAP) || defined(PARALLAXMAP) uniform sampler2D m_NormalMap; varying vec4 wTangent; #endif +#ifdef NORMALSCALE + uniform float m_NormalScale; +#endif varying vec3 wNormal; #ifdef DISCARD_ALPHA @@ -170,7 +177,11 @@ void main(){ //as it's compliant with normal maps generated with blender. //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 //for more explanation. - vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0))); + #ifdef NORMALSCALE + vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)) * vec3(m_NormalScale, m_NormalScale, 1.0)); + #else + vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0))); + #endif normal = normalize(tbnMat * normal); //normal = normalize(normal * inverse(tbnMat)); #else @@ -230,6 +241,12 @@ void main(){ ao = aoRoughnessMetallicValue.rrr; #endif + #ifdef AO_STRENGTH + ao = 1.0 + m_AoStrength * (ao - 1.0); + // sanity check + ao = clamp(ao, 0.0, 1.0); + #endif + float ndotv = max( dot( normal, viewDir ),0.0); for( int i = 0;i < NB_LIGHTS; i+=3){ vec4 lightColor = g_LightData[i]; @@ -313,6 +330,9 @@ void main(){ #if defined(EMISSIVE) || defined (EMISSIVEMAP) #ifdef EMISSIVEMAP vec4 emissive = texture2D(m_EmissiveMap, newTexCoord); + #ifdef EMISSIVE + emissive *= m_Emissive; + #endif #else vec4 emissive = m_Emissive; #endif 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 56b12f166f..57954738a7 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 @@ -38,6 +38,8 @@ MaterialDef PBR Lighting { // Normal map Texture2D NormalMap -LINEAR + // The scalar parameter applied to each normal vector of the normal map + Float NormalScale //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl) Float NormalType : -1.0 @@ -68,6 +70,10 @@ MaterialDef PBR Lighting { // Set to Use Lightmap Texture2D LightMap + // A scalar multiplier controlling the amount of occlusion applied. + // A value of `0.0` means no occlusion. A value of `1.0` means full occlusion. + Float AoStrength + // Set to use TexCoord2 for the lightmap sampling Boolean SeparateTexCoord // the light map is a grayscale ao map, only the r channel will be read. @@ -138,6 +144,7 @@ MaterialDef PBR Lighting { Defines { BASECOLORMAP : BaseColorMap NORMALMAP : NormalMap + NORMALSCALE : NormalScale METALLICMAP : MetallicMap ROUGHNESSMAP : RoughnessMap EMISSIVEMAP : EmissiveMap @@ -159,6 +166,7 @@ MaterialDef PBR Lighting { VERTEX_COLOR : UseVertexColor AO_MAP: LightMapAsAOMap AO_PACKED_IN_MR_MAP : AoPackedInMRMap + AO_STRENGTH : AoStrength NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers HORIZON_FADE: HorizonFade diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib index 293879159e..0e0696c7a3 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; 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 da62023af6..73a2700be3 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; 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 be36021349..0a0c0403c2 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 8dc756686e..7126d94fa8 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-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 0000000000..c3f80f8822 --- /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); + } +} 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 0000000000..02066f4cf3 --- /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); + } +} 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 0000000000..038bdc3fc8 --- /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()); + } +} 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 b9beac7f69..ca981877eb 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-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 0000000000..3cc91ef6f6 --- /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); + } +} 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 0000000000..87283c1c9a --- /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; + } +} 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 d72ee71c82..1232f04809 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 @@ -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. @@ -149,6 +144,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); @@ -231,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/ErrorDialog.java b/jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java deleted file mode 100644 index 26f85be4e0..0000000000 --- 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 0039f56429..645a83b9ed 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 @@ -31,20 +31,24 @@ */ 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; import com.jme3.audio.openal.ALC; import com.jme3.audio.openal.EFX; import com.jme3.system.JmeContext.Type; -import com.jme3.util.Screenshots; -import java.awt.EventQueue; -import java.awt.Graphics2D; -import java.awt.GraphicsEnvironment; -import java.awt.RenderingHints; +import com.jme3.texture.Image; +import com.jme3.texture.image.ColorSpace; +import jme3tools.converters.ImageToAwt; + +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; @@ -53,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; /** * @@ -71,6 +65,9 @@ */ public class JmeDesktopSystem extends JmeSystemDelegate { + public JmeDesktopSystem() { + } + @Override public URL getPlatformAssetConfigURL() { return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"); @@ -80,7 +77,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 +85,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.duplicate(), 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); @@ -116,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 { @@ -352,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 0000000000..b64a2fa84b --- /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 1ce38b0477..a4ad86c55a 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 d6bb9e6c11..85810d4164 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 @@ -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,87 +134,6 @@ public static void registerNativeLibrary(String name, Platform platform, registerNativeLibrary(name, platform, path, null); } - 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"); - - // 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"); - - // OpenAL for LWJGL 3.x - // 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"); - - // 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, "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"); - - // 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.Linux32, null); - registerNativeLibrary("jinput-dx8", Platform.Linux64, null); - registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null); - registerNativeLibrary("jinput-dx8", Platform.MacOSX64, null); - } - private NativeLibraryLoader() { } @@ -241,8 +175,8 @@ public static void setCustomExtractionFolder(String path) { *
    *
  • If a {@link #setCustomExtractionFolder(java.lang.String) custom * extraction folder} has been specified, it is returned. - *
  • If the user can write to the working folder, then it - * is returned.
  • + *
  • If the user can write to "java.io.tmpdir" folder, then it + * is used.
  • *
  • Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} * is used, to prevent collisions, a special subfolder is used * called natives_<hash> where <hash> @@ -257,15 +191,20 @@ public static File getExtractionFolder() { return extractionFolderOverride; } if (extractionFolder == null) { - File workingFolder = new File("").getAbsoluteFile(); - if (!workingFolder.canWrite()) { + File userTempDir = new File(System.getProperty("java.io.tmpdir")); + if (!userTempDir.canWrite()) { setExtractionFolderToUserCache(); } else { try { - File file = new File(workingFolder + File.separator + ".jmetestwrite"); - file.createNewFile(); - file.delete(); - extractionFolder = workingFolder; + File jmeTempDir = new File(userTempDir, "jme3"); + if (!jmeTempDir.exists()) { + jmeTempDir.mkdir(); + } + extractionFolder = new File(jmeTempDir, "natives_" + Integer.toHexString(computeNativesHash())); + + if (!extractionFolder.exists()) { + extractionFolder.mkdir(); + } } catch (Exception e) { setExtractionFolderToUserCache(); } @@ -294,19 +233,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; } @@ -339,10 +273,10 @@ private static void setExtractionFolderToUserCache() { private static int computeNativesHash() { URLConnection conn = null; - try { - String classpath = System.getProperty("java.class.path"); - URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/NativeLibraryLoader.class"); + String classpath = System.getProperty("java.class.path"); + URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/NativeLibraryLoader.class"); + try { StringBuilder sb = new StringBuilder(url.toString()); if (sb.indexOf("jar:") == 0) { sb.delete(0, 4); @@ -359,7 +293,8 @@ private static int computeNativesHash() { int hash = classpath.hashCode() ^ (int) conn.getLastModified(); return hash; } catch (IOException ex) { - throw new UnsupportedOperationException(ex); + throw new UncheckedIOException("Failed to open file: '" + url + + "'. Error: " + ex, ex); } finally { if (conn != null) { try { @@ -466,18 +401,7 @@ public static void extractNativeLibrary(Platform platform, String name, File tar return; } - String fileNameInJar; - if (pathInJar.contains("/")) { - fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1); - } else { - fileNameInJar = pathInJar; - } - URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); - if (url == null) { - url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar); - } - if (url == null) { return; } @@ -486,33 +410,16 @@ public static void extractNativeLibrary(Platform platform, String name, File tar if (library.getExtractedAsName() != null) { loadedAsFileName = library.getExtractedAsName(); } else { - loadedAsFileName = fileNameInJar; + loadedAsFileName = Paths.get(pathInJar).getFileName().toString(); } URLConnection conn = url.openConnection(); - InputStream in = conn.getInputStream(); - + File targetFile = new File(targetDir, loadedAsFileName); - OutputStream out = null; - try { - out = new FileOutputStream(targetFile); - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ex) { - } - } - if (out != null) { - try { - out.close(); - } catch (IOException ex) { - } - } + + try (InputStream in = conn.getInputStream()) { + Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + targetFile.setLastModified(conn.getLastModified()); } } @@ -554,48 +461,19 @@ public static void loadNativeLibrary(String name, boolean isRequired) { // This platform does not require the native library to be loaded. return; } - - final String fileNameInJar; - - if (pathInJar.contains("/")) { - fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1); - } else { - fileNameInJar = pathInJar; - } URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); - - if (url == null) { - // Try the root of the classpath as well. - url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar); - } if (url == null) { - // Attempt to load it as a system library. - String unmappedName = unmapLibraryName(fileNameInJar); - try { - // XXX: HACK. Vary loading method based on library name. - // lwjgl and jinput handle loading by themselves. - if (!name.equals("lwjgl") && !name.equals("jinput")) { - // Need to unmap it from library specific parts. - System.loadLibrary(unmappedName); - logger.log(Level.FINE, "Loaded system installed " - + "version of native library: {0}", unmappedName); - } - } catch (UnsatisfiedLinkError e) { - if (isRequired) { - throw new UnsatisfiedLinkError( - "The required native library '" + unmappedName + "'" - + " was not found in the classpath via '" + pathInJar - + "'. Error message: " + e.getMessage()); - } else if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "The optional native library ''{0}''" + - " was not found in the classpath via ''{1}''" + - ". Error message: {2}", - new Object[]{unmappedName, pathInJar, e.getMessage()}); - } + if (isRequired) { + throw new UnsatisfiedLinkError( + "The required native library '" + library.getName() + "'" + + " was not found in the classpath via '" + pathInJar); + } else if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "The optional native library ''{0}''" + + " was not found in the classpath via ''{1}''.", + new Object[]{library.getName(), pathInJar}); } - return; } @@ -606,16 +484,14 @@ public static void loadNativeLibrary(String name, boolean isRequired) { loadedAsFileName = library.getExtractedAsName(); } else { // Just use the original filename as it is in the JAR. - loadedAsFileName = fileNameInJar; + loadedAsFileName = Paths.get(pathInJar).getFileName().toString(); } File extractionDirectory = getExtractionFolder(); URLConnection conn; - InputStream in; - + try { conn = url.openConnection(); - in = conn.getInputStream(); } catch (IOException ex) { // Maybe put more detail here? Not sure. throw new UncheckedIOException("Failed to open file: '" + url + @@ -623,72 +499,66 @@ public static void loadNativeLibrary(String name, boolean isRequired) { } File targetFile = new File(extractionDirectory, loadedAsFileName); - OutputStream out = null; - try { - if (targetFile.exists()) { - // OK, compare last modified date of this file to - // file in jar - long targetLastModified = targetFile.lastModified(); - long sourceLastModified = conn.getLastModified(); + try (InputStream in = conn.getInputStream()) { + if (isExtractingRequired(conn, targetFile)) { + Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + // NOTE: On OSes that support "Date Created" property, + // this will cause the last modified date to be lower than + // date created which makes no sense + targetFile.setLastModified(conn.getLastModified()); - // Allow ~1 second range for OSes that only support low precision - if (targetLastModified + 1000 > sourceLastModified) { - logger.log(Level.FINE, "Not copying library {0}. " + - "Latest already extracted.", - loadedAsFileName); - return; + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Extracted native library from ''{0}'' into ''{1}''. ", + new Object[]{url, targetFile}); + } + } else { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", + loadedAsFileName); } } - out = new FileOutputStream(targetFile); - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - - in.close(); - in = null; - out.close(); - out = null; + library.getLoadFunction().accept(targetFile.getAbsolutePath()); - // NOTE: On OSes that support "Date Created" property, - // this will cause the last modified date to be lower than - // date created which makes no sense - targetFile.setLastModified(conn.getLastModified()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Loaded native library {0}.", library.getName()); + } } catch (IOException ex) { - if (ex.getMessage().contains("used by another process")) { + /*if (ex.getMessage().contains("used by another process")) { return; - } else { - throw new UncheckedIOException("Failed to extract native " - + "library to: " + targetFile, ex); - } - } finally { - // XXX: HACK. Vary loading method based on library name. - // lwjgl and jinput handle loading by themselves. - if (name.equals("lwjgl") || name.equals("lwjgl3")) { - System.setProperty("org.lwjgl.librarypath", - extractionDirectory.getAbsolutePath()); - } else if (name.equals("jinput")) { - System.setProperty("net.java.games.input.librarypath", - extractionDirectory.getAbsolutePath()); - } else { - // all other libraries (openal, bulletjme, custom) - // will load directly in here. - System.load(targetFile.getAbsolutePath()); - } - - if(in != null){ - try { in.close(); } catch (IOException ex) { } - } - if(out != null){ - try { out.close(); } catch (IOException ex) { } - } + }*/ + + throw new UncheckedIOException("Failed to extract native library to: " + + targetFile, ex); } + } - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''", - new Object[]{url, targetFile}); + /** + * Checks if library extraction is required by comparing source and target + * last modified date. Returns true if target file does not exist. + * + * @param conn the source file + * @param targetFile the target file + * @return false if target file exist and the difference in last modified date is + * less than 1 second, true otherwise + */ + private static boolean isExtractingRequired(URLConnection conn, File targetFile) { + if (!targetFile.exists()) { + // Extract anyway if the file doesn't exist + return true; } + + // OK, if the file exists then compare last modified date + // of this file to file in jar + long targetLastModified = targetFile.lastModified(); + long sourceLastModified = conn.getLastModified(); + + // Allow ~1 second range for OSes that only support low precision + return Math.abs(sourceLastModified - targetLastModified) >= 1000; + + // Note extraction should also work fine if user who was using + // a newer version of library, downgraded to an older version + // which will make above check invalid and extract it again. } - } 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 111c664eda..032c8457eb 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 @@ -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 @@ -120,6 +123,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; @@ -276,4 +289,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-effects/src/main/java/com/jme3/water/WaterFilter.java b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java index be110935d3..509cbd0ed0 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 diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index a6e9888edf..60912ff1a8 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -25,7 +25,9 @@ dependencies { implementation project(':jme3-niftygui') implementation project(':jme3-plugins') 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-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index d8dafae5a6..28cf10064d 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()); diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java index 1f1909292a..da6b4e9a2f 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,9 @@ import com.jme3.audio.plugins.WAVLoader; import com.jme3.system.AppSettings; import com.jme3.system.JmeSystem; +import com.jme3.system.NativeLibraries; +import com.jme3.system.NativeLibraryLoader; + import java.io.*; import javax.swing.JFileChooser; @@ -52,6 +55,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(NativeLibraries.Lwjgl.getName(), false); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.OpenAL.getName(), false); + } + public TestMusicPlayer() { initComponents(); setLocationRelativeTo(null); diff --git a/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java b/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java index d8c6aa8c92..9dab109021 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); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java index 32bb9c2402..8c1c712603 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); } } } 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 0000000000..b466815dfb --- /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; + } +} 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 0000000000..e169d27850 --- /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 0000000000..22bcc18e26 --- /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/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java index d98583244b..18b09edbf2 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,8 @@ 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.Tweens; +import com.jme3.anim.tween.action.*; import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.app.ChaseCameraAppState; import com.jme3.app.SimpleApplication; @@ -157,9 +157,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)); @@ -173,17 +174,44 @@ 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); } }, "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) { @@ -217,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"); diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancingWithWaterFilter.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancingWithWaterFilter.java new file mode 100644 index 0000000000..f0fe8f6b7e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancingWithWaterFilter.java @@ -0,0 +1,90 @@ +/* + * 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.scene.instancing; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.scene.Geometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Box; +import com.jme3.water.WaterFilter; + +/** + * A test case for using instancing with shadow filter. This is a test case + * for issue 2007 (Instanced objects are culled when using the WaterFilter). + * + * If test succeeds, all the boxes in the camera frustum will be rendered. If + * test fails, some of the boxes that are in the camera frustum will be culled. + * + * @author Ali-RS + */ +public class TestInstancingWithWaterFilter extends SimpleApplication { + public static void main(String[] args) { + TestInstancingWithWaterFilter test = new TestInstancingWithWaterFilter(); + test.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10); + + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1)); + rootNode.addLight(light); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setBoolean("UseInstancing", true); + + Box mesh = new Box(0.5f, 0.5f, 0.5f); + + InstancedNode instanceNode = new InstancedNode("TestInstancedNode"); + //instanceNode.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(instanceNode); + + for (int i = 0; i < 200; i++) { + Geometry obj = new Geometry("TestBox" + i, mesh); + obj.setMaterial(mat); + obj.setLocalTranslation(i, i, 0); + instanceNode.attachChild(obj); + } + instanceNode.instance(); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + WaterFilter waterFilter = new WaterFilter(rootNode, light.getDirection()); + fpp.addFilter(waterFilter); + viewPort.addProcessor(fpp); + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java index 23aa76f3bf..d380201516 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 69bc1ba757..0bf655aeee 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java @@ -2,8 +2,6 @@ import com.jme3.app.SimpleApplication; import com.jme3.app.state.ScreenshotAppState; -import com.jme3.asset.plugins.HttpZipLocator; -import com.jme3.asset.plugins.ZipLocator; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; @@ -28,7 +26,6 @@ import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; -import java.io.File; public class TerrainGridTileLoaderTest extends SimpleApplication { @@ -46,13 +43,11 @@ public static void main(final String[] args) { @Override public void simpleInitApp() { - File file = new File("TerrainGridTestData.zip"); - if (!file.exists()) { - assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class); - } else { - assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class); - } - + /* + * Note: this test uses the "TerrainGrid" assets (from jme3-testdata), + * _not_ the "TerrainGridTestData.zip" assets + * (from jme3-examples and the Google Code archives). + */ this.flyCam.setMoveSpeed(100f); ScreenshotAppState state = new ScreenshotAppState(); this.stateManager.attach(state); diff --git a/jme3-examples/src/main/resources/TestIssue1903.j3m b/jme3-examples/src/main/resources/TestIssue1903.j3m new file mode 100644 index 0000000000..bf9386fd54 --- /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 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 dfbcc9c4a6..009195c776 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"); @@ -216,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-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java index 820e387e54..77f41c1f82 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-jbullet/build.gradle b/jme3-jbullet/build.gradle index 39bb120368..1a2621a420 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 3ee27e1eea..b8df7695ff 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-jogg/build.gradle b/jme3-jogg/build.gradle index aa99d11d93..a039b96bca 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-vorbis:1.0.3' } diff --git a/jme3-lwjgl/build.gradle b/jme3-lwjgl/build.gradle index 6bfe60863a..846c2fd254 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.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. * See https://hub.jmonkeyengine.org/t/linux-gamepad-input-on-jme3-lwjgl-splits-input-between-two-logical-gamepads 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 60986d0432..b436806dca 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; @@ -225,14 +235,14 @@ protected void loadNatives() { if (JmeSystem.isLowPermissions()) { return; } - if ("LWJGL".equals(settings.getAudioRenderer())) { - NativeLibraryLoader.loadNativeLibrary("openal", true); + if (AppSettings.LWJGL_OPENAL.equals(settings.getAudioRenderer())) { + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.OpenAL.getName(), true); } if (settings.useJoysticks()) { - NativeLibraryLoader.loadNativeLibrary("jinput", true); - NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.JInput.getName(), true); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.JInputDX8.getName(), true); } - NativeLibraryLoader.loadNativeLibrary("lwjgl", true); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.Lwjgl.getName(), true); } protected int getNumSamplesToUse() { int samples = 0; @@ -307,8 +317,8 @@ private void initContext(boolean first) { glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); } renderer = new GLRenderer(gl, glext, glfbo); - renderer.initialize(); } + renderer.initialize(); } else { throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); } @@ -509,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-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index cd00e1c3d5..1db33942e8 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()); } @@ -127,6 +150,7 @@ protected void createContext(AppSettings settings) throws LWJGLException{ Display.setVSyncEnabled(settings.isVSync()); if (created.get() && !pixelFormatChanged) { + renderer.resetGLObjects(); Display.releaseContext(); Display.makeCurrent(); Display.update(); 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 7118338229..f9899fe8b3 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 baa28243d0..0a0a6bcd54 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; @@ -237,6 +247,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(); 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 9962b7218d..c2b5e47402 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 @@ -87,10 +87,16 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { static { RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL30, () -> { + // Based on GLFW docs for OpenGL version below 3.2, + // GLFW_OPENGL_ANY_PROFILE must be used. + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); }); RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL31, () -> { + // Based on GLFW docs for OpenGL version below 3.2, + // GLFW_OPENGL_ANY_PROFILE must be used. + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); }); @@ -287,7 +293,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(); } @@ -799,4 +805,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-niftygui/build.gradle b/jme3-niftygui/build.gradle index 28ed886d01..088cb562ef 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-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java index 714c67b928..10605ed08a 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() { diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 17fcd7ab56..b88f0a64fd 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') } 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 7a0bf1d58a..02413e601f 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); } @@ -328,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/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java index cd701e5066..45b71b224a 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 3065ce88c3..b010e66a92 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,11 +54,51 @@ 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; + } + } } } + + 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() { @@ -95,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 2e302fc4d3..4ae677700f 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); 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 9b1e6ad1b0..b1f376235f 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 28b051f376..6a916464db 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': 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 6f2ba8e08a..e8fa6403e6 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]; 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 8e3d9c5324..678e88eae9 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"); } } 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 5b8908948c..5e76201819 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 @@ -51,12 +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()); } 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 6daad31d6f..2475c01ea9 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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<>(); @@ -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"); @@ -634,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)); @@ -641,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")); @@ -655,8 +659,26 @@ public Material readMaterial(int materialIndex) throws IOException { adapter.setParam("normalTexture", normal); if (normal != null) { useNormalsFlag = true; + + JsonObject normalTexture = matData.getAsJsonObject("normalTexture"); + Float normalScale = getAsFloat(normalTexture, "scale"); + if (normalScale != null) { + adapter.setParam("normalScale", normalScale); + } } - 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"))); + } + + Float occlusionStrength = occlusionJson != null ? getAsFloat(occlusionJson, "strength") : null; + if (occlusionStrength != null) { + adapter.setParam("occlusionStrength", occlusionStrength); + } + adapter.setParam("emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture"))); return adapter.getMaterial(); @@ -1282,8 +1304,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; } 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 07ab0a1f24..e5b42d99a1 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.*; @@ -383,20 +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 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: - b = stream.readByte(); - return b / 255f; + c = stream.readUnsignedByte(); + return c / 255f; case Short: - b = stream.readByte(); - return Math.max(b / 32767f, -1f); + c = stream.readShort(); + return Math.max(c / 32767f, -1f); case UnsignedShort: - b = stream.readByte(); - return b / 65535f; + c = stream.readUnsignedShort(); + return c / 65535f; default: //we have a regular float return stream.readFloat(); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java index ee7219efcb..cb2639dc8f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.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 @@ -50,7 +50,7 @@ */ public abstract class MaterialAdapter { - private Map paramsMapping = new HashMap<>(); + private final Map paramsMapping = new HashMap<>(); private Material mat; private AssetManager assetManager; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java index b184c402d8..993d0411a7 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.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 @@ -40,7 +40,9 @@ public abstract class PBRMaterialAdapter extends MaterialAdapter { public PBRMaterialAdapter() { addParamMapping("normalTexture", "NormalMap"); + addParamMapping("normalScale", "NormalScale"); addParamMapping("occlusionTexture", "LightMap"); + addParamMapping("occlusionStrength", "AoStrength"); addParamMapping("emissiveTexture", "EmissiveMap"); addParamMapping("emissiveFactor", "Emissive"); addParamMapping("alphaMode", "alpha"); 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 0000000000..d54382d8ac --- /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."); + } + } +} 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 fd2c0bbfbd..950466b4ba 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 be8fc3573b..e5dfa75965 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); 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 a120120602..b4ffc5a12d 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()); } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java index 6401939315..f96d13c384 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.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 @@ -321,8 +321,7 @@ public boolean load() { logger.fine("Created heightmap using Particle Deposition"); - - return false; + return true; // success } /** 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 664d86212f..14899a0eca 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/AfflictionLib.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AfflictionLib.glsllib index 8718cd3842..2c96614adb 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){ 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 ac91e8eb2e..f4dfa78cd1 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 "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; } diff --git a/jme3-testdata/build.gradle b/jme3-testdata/build.gradle index 3650706333..7d82dc72fb 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" } 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 cbbde4005f..fa0956b2b2 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; 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 5d9be70c3f..02ab733230 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/jme3-vr/src/main/java/com/jme3/input/vr/VRInputAPI.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRInputAPI.java index 53d99a8014..113a23c1f2 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputAPI.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/VRInputAPI.java @@ -12,11 +12,11 @@ public interface VRInputAPI { /** - * Registers an action manifest. An actions manifest is a file that defines "actions" a player can make. + * Registers an action manifest. An action manifest is a file that defines "actions" a player can make. * (An action is an abstract version of a button press). The action manifest may then also include references to * further files that define default mappings between those actions and physical buttons on the VR controllers. * - * Note that registering an actions manifest will deactivate legacy inputs (i.e. methods such as {@link #isButtonDown} + * Note that registering an actions manifest will deactivate legacy inputs. i.e. methods such as {@link #isButtonDown} * will no longer work * * See https://github.com/ValveSoftware/openvr/wiki/Action-manifest for documentation on how to create an @@ -138,7 +138,8 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Check if the given button is down (more generally if the given input type is activated). * - * Deprecated as should use an actions manifest approach. See {@link #registerActionManifest}. Note; action based will only work with the OpenVR api + * @deprecated Use the action-manifest approach instead. See {@link #registerActionManifest}. + * Note: action-manifest will only work with the OpenVR api. * * @param controllerIndex the index of the controller to check. * @param checkButton the button / input to check. @@ -150,20 +151,25 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Check if the given button / input from the given controller has been just pressed / activated. * - * Deprecated as should use an actions manifest approach. See {@link #registerActionManifest}. Note; action based will only work with the OpenVR api + * @deprecated Use the action-manifest approach instead. See {@link #registerActionManifest}. + * Note: action-manifest will only work with the OpenVR api. * * @param controllerIndex the index of the controller. * @param checkButton the button / input to check. - * @return true if the given button / input from the given controller has been just pressed / activated and false otherwise. + * @return true if the given input from the given controller has just been activated, + * false otherwise. */ @Deprecated public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton); /** - * Reset the current activation of the inputs. After a call to this method, all input activation is considered as new activation. + * Reset the current activation of the inputs. After a call to this method, any input activation is + * considered a new activation. + * * @see #wasButtonPressedSinceLastCall(int, VRInputType) * - * Deprecated as should use an actions manifest approach. See {@link #registerActionManifest}. Note; action based will only work with the OpenVR api + * @deprecated Use the action-manifest approach instead. See {@link #registerActionManifest}. + * Note: action-manifest will only work with the OpenVR api. */ @Deprecated public void resetInputSinceLastCall(); @@ -171,7 +177,8 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Get the controller axis delta from the last value. * - * Deprecated as should use an actions manifest approach. See {@link #registerActionManifest}. Note; action based will only work with the OpenVR api + * @deprecated Use action-manifest approach instead. See {@link #registerActionManifest}. + * Note: action-manifest will only work with the OpenVR api. * * @param controllerIndex the index of the controller. * @param forAxis the axis. @@ -198,9 +205,11 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Get the axis value for the given input on the given controller. - * This value is the {@link #getAxisRaw(int, VRInputType) raw value} multiplied by the {@link #getAxisMultiplier() axis multiplier}. + * This value is the {@link #getAxisRaw(int, VRInputType) raw value} multiplied by the + * {@link #getAxisMultiplier() axis multiplier}. * - * Deprecated as should use an actions manifest approach. See {@link #registerActionManifest}. Note; action based will only work with the OpenVR api + * @deprecated Use action-manifest approach instead. See {@link #registerActionManifest}. + * Note: action-manifest will only work with the OpenVR api. * * @param controllerIndex the index of the controller. * @param forAxis the axis. @@ -214,7 +223,8 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Get the axis value for the given input on the given controller. * - * Deprecated as should use an actions manifest approach. See {@link #registerActionManifest} Note; action based will only work with the OpenVR api + * @deprecated Use the action-manifest approach. See {@link #registerActionManifest}. + * Note: action-manifest will only work with the OpenVR api. * * @param controllerIndex the index of the controller. * @param forAxis the axis. @@ -333,7 +343,7 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Trigger a haptic pulse on the selected controller for the duration given in parameters (in seconds). * - * Deprecated, use triggerHapticAction instead (as it has more options and doesn't use deprecated methods) + * @deprecated Use triggerHapticAction instead - it has more options and doesn't use deprecated methods. * * @param controllerIndex the index of the controller. * @param seconds the duration of the pulse in seconds. @@ -344,9 +354,10 @@ default AnalogActionState getAnalogActionState( String actionName, String restri /** * Triggers a haptic action (aka a vibration). * - * Note if you want a haptic action in only one hand that is done either by only binding the action to one hand in - * the action manifest's standard bindings or by binding to both and using {@link #triggerHapticAction(String, float, float, float, String)} - * to control which input it gets set to at run time + * Note: if you want a haptic action in only one hand, you can either bind the action to one hand in + * the action manifest's standard bindings or bind to both and use + * {@link #triggerHapticAction(String, float, float, float, String)} + * to control which input it gets set to at runtime. * * @param actionName The name of the action. Will be something like /actions/main/out/vibrate * @param duration how long in seconds the @@ -360,16 +371,18 @@ default void triggerHapticAction( String actionName, float duration, float frequ /** * Triggers a haptic action (aka a vibration) restricted to just one input (e.g. left or right hand). * - * Note that restrictToInput only restricts, it must still be bound to the input you want to send the haptic to in - * the action manifest default bindings. + * Note: restrictToInput only restricts which input is used at runtime. You must still bind the input + * you want to send the haptic to in the action manifest default bindings. * - * This method is typically used to bind the haptic to both hands then decide at run time which hand to sent to * + * This method is typically used by binding the haptic to both hands, and then deciding at runtime which + * hand it should be sent to. * * @param actionName The name of the action. Will be something like /actions/main/out/vibrate * @param duration how long in seconds the * @param frequency in cycles per second * @param amplitude between 0 and 1 - * @param restrictToInput the input to restrict the action to. E.g. /user/hand/right, /user/hand/left. Or null, which means "any input" + * @param restrictToInput the input to restrict the action to, such as /user/hand/right or + * /user/hand/left. null means "any input". */ default void triggerHapticAction( String actionName, float duration, float frequency, float amplitude, String restrictToInput){ throw new UnsupportedOperationException("Action manifests are not supported for the currently used VR API"); diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVR.java index 30c4ec2c99..c0574bc46d 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVR.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVR.java @@ -48,7 +48,7 @@ public class LWJGLOpenVR implements VRAPI { private Vector3f hmdPoseLeftEyeVec, hmdPoseRightEyeVec, hmdSeatToStand; - private LWJGLOpenVRInput VRinput; + private LWJGLOpenVRInput vrInput; private VREnvironment environment = null; @@ -91,7 +91,7 @@ public LWJGLOpenVR(VREnvironment environment){ @Override public LWJGLOpenVRInput getVRinput() { - return VRinput; + return vrInput; } @Override @@ -158,9 +158,9 @@ public boolean initialize() { hmdDisplayFrequency.get(0); TrackedDevicePose.create(VR.k_unMaxTrackedDeviceCount); // init controllers for the first time - VRinput = new LWJGLOpenVRInput(environment); - VRinput.init(); - VRinput.updateConnectedControllers(); + vrInput = new LWJGLOpenVRInput(environment); + vrInput.init(); + vrInput.updateConnectedControllers(); // init bounds & chaperone info LWJGLOpenVRBounds bounds = new LWJGLOpenVRBounds(); @@ -431,4 +431,4 @@ public void setTrackingSpace(boolean isSeated){ public Matrix4f[] getPoseMatrices() { return poseMatrices; } -} \ No newline at end of file +} 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 4b545daf29..7338864dd8 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; 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 c32f560792..0ec3f7498a 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; @@ -109,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(NativeLibraries.BulletJme.getName(), true); } - - NativeLibraryLoader.loadNativeLibrary("glfw-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("jemalloc-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("lwjgl3", true); } /** 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 c43cc50c63..5c21cd6e92 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. /* diff --git a/natives-snapshot.properties b/natives-snapshot.properties index 1d83fcfd57..2e7a470a18 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=ec136bac246ac6ff21426b227076541ce036555a +natives.snapshot=3d49531fb98c8e41bf39d6a2167d5c3b95557148 diff --git a/settings.gradle b/settings.gradle index 78341dfa85..3f6acceb8e 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' 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